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 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 117
WorkHourCalculator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 10
3540.00
0.00% covered (danger)
0.00%
0 / 117
 roundDate
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 setTime
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 add
n/a
0 / 0
1
n/a
0 / 0
 sub
n/a
0 / 0
1
n/a
0 / 0
 workDaysArray
0.00% covered (danger)
0.00%
0 / 1
156.00
0.00% covered (danger)
0.00%
0 / 24
 addWorkHours
0.00% covered (danger)
0.00%
0 / 1
90.00
0.00% covered (danger)
0.00%
0 / 20
 addHoursInSameDay
0.00% covered (danger)
0.00%
0 / 1
20.00
0.00% covered (danger)
0.00%
0 / 6
 subWorkHours
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 20
 numberOfWorkHours
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 17
 hourToWorkableHour
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 13
 hoursLeftInDay
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 4
 _hoursLeftInDay
0.00% covered (danger)
0.00%
0 / 1
56.00
0.00% covered (danger)
0.00%
0 / 11
<?php
namespace ia\Work\WorkDay;
class WorkHourCalculator extends WorkDayCalculator {
    protected $byHour = true;
    protected function roundDate($anyDate) {
        return self::toDateHourImmutable($anyDate); // Hour
    }
    protected function setTime(\DateTimeImmutable $dateTime) {
        return $dateTime->setTime($this->firstWorkHourByWeekDay[$dateTime->format('w')], 0, 0); // por hour
    }
    public function add($dateTime, $numHours) { return $this->addWorkHours($dateTime, $numHours); }
    public function sub($dateTime, $numHours) { return $this->subWorkHours($dateTime, $numHours); }
// BY DAY _____________________________________________
    /**
     * Array of workable days between dateTimes $anyDateFrom to $anyDateTo
     *
     * @param string|integer|DateTime|DateTimeImmutable $anyDateFrom
     * @param string|integer|DateTime|DateTimeImmutable $anyDateTo
     * @return array ['Y-m-d'=>Number_WorkableHours, ...] orderd from $anyDateFrom to $anyDateTo
     */
    public function workDaysArray($anyDateFrom, $anyDateTo = null) {
        $firstDate = $this->roundDate($anyDateFrom);
        if($anyDateTo == null) {
            $anyDateTo = $anyDateFrom;
        }
        $lastDate = $this->roundDate($anyDateTo);
        if(\is_string($anyDateTo) && \strlen($anyDateTo) === 10 && !$this->_is_nonWorkDay($lastDate)) {
            $lastDate = $lastDate->setTime($this->lastWorkHourByWeekDay[(int)$lastDate->format('w')], 0, 0);
        }
        $swaped = self::orderd($firstDate, $lastDate);
        $lastDayIsWorkDay = !$this->_is_nonWorkDay($lastDate);
        if($firstDate->format('Y-m-d') === $lastDate->format('Y-m-d')) {
            if(!$lastDayIsWorkDay) {
                return [];
            }
            if( $this->hourToWorkableHour($firstDate) == $this->hourToWorkableHour($lastDate)) {
                return [$firstDate->format('Y-m-d') => 0];
            }
            return [$firstDate->format('Y-m-d') => $this->hoursLeftInDay($firstDate) - $this->hoursLeftInDay($lastDate)];
        }
        $workDays = [];
        while($lastDate > $firstDate) {
            if(!$this->_is_nonWorkDay($firstDate)) {
                $workDays[$firstDate->format('Y-m-d')] = $this->totalWorkHourByWeekDay[$firstDate->format('w')];
            }
            $firstDate = $firstDate->add($this->dateIntervalOneDay);
        }
        if($lastDayIsWorkDay) {
            $workDays[$lastDate->format('Y-m-d')] =  $this->totalWorkHourByWeekDay[$lastDate->format('w')] - $this->hoursLeftInDay($lastDate);
        }
        if($swaped === -1) {
            return \array_reverse($workDays, true);
        }
        return $workDays;
    }
// BY HOUR ____________________________________________
    /**
     * Add workable hours to a date
     *
     * @param string|integer|DateTime|DateTimeImmutable $anyDate
     * @param integer|string $numHours
     * @return DateTimeImmutable with $numDays workable hours added, ie after
     */
    public function addWorkHours($anyDate, $numHours) {
        $numHours = (int)$numHours;
        if($numHours < 0) {
            return $this->subWorkHours($anyDate, -$numHours);
        }
        $dateTime = $this->hourToWorkableHour(self::toDateHourImmutable($anyDate));
        if($this->_is_nonWorkDay($dateTime)) {
            $dateTime = $this->nextWorkDay($dateTime);
        }
        if($numHours === 0) {
            return $dateTime;
        }
        $dayOfWeek = (int)$dateTime->format('w');
        $currentHour = (int)$dateTime->format('G');
        // casos comunes optimizados.
        if( $currentHour === $this->firstWorkHourByWeekDay[$dayOfWeek]) {
            $totalHoursInDay = $this->totalWorkHourByWeekDay[$dayOfWeek];
            if($numHours === $totalHoursInDay) {
                return $this->nextWorkDay($dateTime);
            }
            if($numHours > $totalHoursInDay) {
                return $this->addWorkHours( $this->nextWorkDay($dateTime), $numHours - $totalHoursInDay);
            }
        }
        $hoursLeftInDay = $this->_hoursLeftInDay($currentHour, $dayOfWeek);
        if($hoursLeftInDay < 0) return $this->nextWorkDay($dateTime); // @codeCoverageIgnore
        if($numHours > $hoursLeftInDay) {
            return $this->addWorkHours( $this->nextWorkDay($dateTime), $numHours - $hoursLeftInDay);
        }
        return $this->addHoursInSameDay($dateTime, $numHours, $currentHour);
    }
    /**
     * Adds hour in the same day to a date
     *
     * @param DateTimeImmutable $dateTime
     * @param int $needHours
     * @param int $currentHour
     * @return DateTimeImmutable $dateTime with hours added
     */
    private function addHoursInSameDay(\DateTimeImmutable $dateTime, int $needHours, int $currentHour) {
        foreach($this->workHoursByWeekDay[(int)$dateTime->format('w')] as $rango) {
            if($currentHour <= $rango[1]) {
                $hoursLeftInRange = $rango[1] - \max($rango[0], $currentHour);
                if($hoursLeftInRange >= $needHours) {
                    return $dateTime->setTime(\max($rango[0], $currentHour) + $needHours, 0, 0);
                }
                $needHours = \max($needHours - $hoursLeftInRange, 0);
            }
        }
        return $this->addWorkHours($this->nextWorkDay($dateTime), $needHours); // @codeCoverageIgnore
    }
    /**
     * Add workable hours to a date
     *
     * @param string|integer|DateTime|DateTimeImmutable $anyDate
     * @param integer|string $numHours
     * @return DateTimeImmutable with $numDays workable hours added, ie after
     */
    public function subWorkHours($anyDate, $numHours) {
        if($numHours < 0) {
            return $this->addWorkHours($anyDate, -$numHours);
        }
        $dateTime = $this->hourToWorkableHour(self::toDateHourImmutable($anyDate) );
        if($numHours === 0) {
            return $dateTime;
        }
        $dayOfWeek = (int)$dateTime->format('w');
        $currentHour = $dateTime->format('G');
        $moveToStart = false;
        foreach($this->workHoursByWeekDayReversed[$dayOfWeek] as $rango) {
            if($moveToStart) {
                $currentHour = $rango[1];
            }
            if($currentHour >= $rango[0] && $currentHour <= $rango[1]  ) {
                $hoursLeftInRange = \min($rango[1], $currentHour) - $rango[0];
                if($hoursLeftInRange >= $numHours) {
                    return $dateTime->setTime($currentHour - $numHours, 0, 0);
                }
                $numHours = \max($numHours - $hoursLeftInRange, 0);
                $moveToStart=true;
            }
        }
        $prevDay = $this->prevWorkDay($dateTime);
        $prevDay = $prevDay->setTime($this->lastWorkHourByWeekDay[(int)$prevDay->format('w')], 0, 0);
        return $this->subWorkHours($prevDay, $numHours);
    }
    /**
     * Number of workable between hours dateTimes $anyDateFrom and $anyDateTo
     *
     * @param string|integer|DateTime|DateTimeImmutable $anyDateFrom
     * @param string|integer|DateTime|DateTimeImmutable $anyDateTo
     * @return int Number of workable hours between dateTimes $anyDateFrom and $anyDateTo
     */
    public function numberOfWorkHours($anyDateFrom, $anyDateTo) {
        $start = $this->hourToWorkableHour( self::toDateHourImmutable($anyDateFrom) );
        if($this->_is_nonWorkDay($start)) {
            $start = $this->nextWorkDay($start);
        }
        $end = $this->hourToWorkableHour( self::toDateHourImmutable($anyDateTo) );
        if($this->_is_nonWorkDay($end)) {
            $end = $this->nextWorkDay($end);
        }
        $swaped = self::orderd($start, $end);
        $endYmd = $end->format('Y-m-d');
        $hours = 0;
        while($end >= $start) {
            if($start->format('Y-m-d') !== $endYmd) {
                $hours += $this->_hoursLeftInDay((int)$start->format('G'), (int)$start->format('w'));
            } else {
                $dayOfWeek = (int)$start->format('w');
                $hours += $this->_hoursLeftInDay((int)$start->format('G'), $dayOfWeek) - $this->_hoursLeftInDay((int)$end->format('G'), $dayOfWeek);
                break;
            }
            $start = $this->nextWorkDay($start);
        }
        return $swaped * $hours;
    }
// ________________________________________________________________________________________________________________________________________________________________________
    public function hourToWorkableHour(\DateTimeImmutable $dateTime) {
            $dayOfWeek = (int)$dateTime->format('w');
            if(empty($this->firstWorkHourByWeekDay[$dayOfWeek])) {
                return $dateTime;
            }
            $currentHour = $dateTime->format('G');
            if($currentHour < $this->firstWorkHourByWeekDay[$dayOfWeek]) {
                return $dateTime->setTime($this->firstWorkHourByWeekDay[$dayOfWeek], 0, 0);
            }
            if($currentHour > $this->lastWorkHourByWeekDay[$dayOfWeek]) {
                return $dateTime->setTime($this->lastWorkHourByWeekDay[$dayOfWeek], 0, 0);
            }
            foreach($this->workHoursByWeekDay[$dayOfWeek] as $rango) {
                if($currentHour >= $rango[0] && $currentHour <= $rango[1]) {
                    return $dateTime;
                }
                if($currentHour < $rango[0]) {
                    return $dateTime->setTime($rango[0], 0, 0);
                }
            }
            return $dateTime->setTime($this->lastWorkHourByWeekDay[$dayOfWeek], 0, 0); // @codeCoverageIgnore
    }
    /**
     * How many workable hours are left in $anyDate
     *
     * @param  string|integer|DateTime|DateTimeImmutable $anyDate
     * @return int number of workable hours are left in the day
     */
    public function hoursLeftInDay($anyDate) {
        $dateTime = self::toDateHourImmutable($anyDate);
        if($this->_is_nonWorkDay($dateTime)) {
            return 0;
        }
        return $this->_hoursLeftInDay((int)$dateTime->format('G'), (int)$dateTime->format('w'));
    }
    /**
     * From current hour, how many workable hours are left in the day
     *
     * @param int $currentHour
     * @param int $dayOfWeek
     * @return int number of workable hours are left in the day
     */
    private function _hoursLeftInDay(int $currentHour, $dayOfWeek) {
        if($currentHour <= $this->firstWorkHourByWeekDay[$dayOfWeek]) {
            return $this->totalWorkHourByWeekDay[$dayOfWeek];
        }
        if($currentHour >= $this->lastWorkHourByWeekDay[$dayOfWeek]) {
            return 0;
        }
        $hours = 0;
        foreach($this->workHoursByWeekDay[$dayOfWeek] as $rango) {
            if($currentHour >= $rango[0] && $currentHour <= $rango[1]) {
                $hours += $rango[1] - $currentHour;
            } elseif($currentHour < $rango[0]) {
                $hours += $rango[1] - $rango[0];
            }
        }
        return $hours;
    }
}