setTime(0, 0); $end_dt = new DateTime($end); $end_dt->setTime(23, 59); $timetable = $this->calculate($user_id, $start_dt, $end_dt); // The user has no timetable if (empty($this->week)) { return $events; } $results = array(); foreach ($events as $event) { $results = array_merge($results, $this->calculateEventIntersect($event, $timetable)); } return $results; } /** * Get a serie of events based on the timetable and the provided event * * @access public * @param integer $user_id * @param array $events Time tracking data * @param string $start ISO8601 date * @param string $end ISO8601 date * @return array */ public function calculateEventIntersect(array $event, array $timetable) { $events = array(); foreach ($timetable as $slot) { $start_ts = $slot[0]->getTimestamp(); $end_ts = $slot[1]->getTimestamp(); if ($start_ts > $event['end']) { break; } if ($event['start'] <= $start_ts) { $event['start'] = $start_ts; } if ($event['start'] >= $start_ts && $event['start'] <= $end_ts) { if ($event['end'] >= $end_ts) { $events[] = array_merge($event, array('end' => $end_ts)); } else { $events[] = $event; break; } } } return $events; } /** * Calculate effective worked hours by taking into consideration the timetable * * @access public * @param integer $user_id * @param \DateTime $start * @param \DateTime $end * @return float */ public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end) { $end_timetable = clone($end); $end_timetable->setTime(23, 59); $timetable = $this->calculate($user_id, $start, $end_timetable); $found_start = false; $hours = 0; // The user has no timetable if (empty($this->week)) { return $this->dateParser->getHours($start, $end); } foreach ($timetable as $slot) { $isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]); $isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]); // Start and end are within the same time slot if ($isStartSlot && $isEndSlot) { return $this->dateParser->getHours($start, $end); } // We found the start slot if (! $found_start && $isStartSlot) { $found_start = true; $hours = $this->dateParser->getHours($start, $slot[1]); } else if ($found_start) { // We found the end slot if ($isEndSlot) { $hours += $this->dateParser->getHours($slot[0], $end); break; } else { // Sum hours of the intermediate time slots $hours += $this->dateParser->getHours($slot[0], $slot[1]); } } } // The start date was not found in regular hours so we get the nearest time slot if (! empty($timetable) && ! $found_start) { $slot = $this->findClosestTimeSlot($start, $timetable); if ($start < $slot[0]) { return $this->calculateEffectiveDuration($user_id, $slot[0], $end); } } return $hours; } /** * Find the nearest time slot * * @access public * @param DateTime $date * @param array $timetable * @return array */ public function findClosestTimeSlot(DateTime $date, array $timetable) { $values = array(); foreach ($timetable as $slot) { $t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp()); $t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp()); $values[] = min($t1, $t2); } asort($values); return $timetable[key($values)]; } /** * Get the timetable for a user for a given date range * * @access public * @param integer $user_id * @param \DateTime $start * @param \DateTime $end * @return array */ public function calculate($user_id, DateTime $start, DateTime $end) { $timetable = array(); $this->day = $this->timetableDay->getByUser($user_id); $this->week = $this->timetableWeek->getByUser($user_id); $this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); $this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) { $week_day = $today->format('N'); $timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day)); $timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day)); } return $timetable; } /** * Return worked time slots for the given day * * @access public * @param \DateTime $today * @param string $week_day * @return array */ public function getWeekSlots(DateTime $today, $week_day) { $slots = array(); $dayoff = $this->getDayOff($today); if (! empty($dayoff) && $dayoff['all_day'] == 1) { return array(); } foreach ($this->week as $slot) { if ($week_day == $slot['day']) { $slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today)); } } return $slots; } /** * Get the overtime time slots for the given day * * @access public * @param \DateTime $today * @param string $week_day * @return array */ public function getOvertimeSlots(DateTime $today, $week_day) { $slots = array(); foreach ($this->overtime as $slot) { $day = new DateTime($slot['date']); if ($week_day == $day->format('N')) { if ($slot['all_day'] == 1) { $slots = array_merge($slots, $this->getDaySlots($today)); } else { $slots[] = $this->getTimeSlot($slot, $day); } } } return $slots; } /** * Get worked time slots and remove time off * * @access public * @param array $slot * @param array $dayoff * @param \DateTime $today * @return array */ public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today) { $slots = array(); if (! empty($dayoff) && $dayoff['start'] < $slot['end']) { if ($dayoff['start'] > $slot['start']) { $slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today); } if ($dayoff['end'] < $slot['end']) { $slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today); } } else { $slots[] = $this->getTimeSlot($slot, $today); } return $slots; } /** * Get regular day work time slots * * @access public * @param \DateTime $today * @return array */ public function getDaySlots(DateTime $today) { $slots = array(); foreach ($this->day as $day) { $slots[] = $this->getTimeSlot($day, $today); } return $slots; } /** * Get the start and end time slot for a given day * * @access public * @param array $slot * @param \DateTime $today * @return array */ public function getTimeSlot(array $slot, DateTime $today) { $date = $today->format('Y-m-d'); return array( new DateTime($date.' '.$slot['start']), new DateTime($date.' '.$slot['end']), ); } /** * Return day off time slot * * @access public * @param \DateTime $today * @return array */ public function getDayOff(DateTime $today) { foreach ($this->timeoff as $day) { if ($day['date'] === $today->format('Y-m-d')) { return $day; } } return array(); } }