1 month, the first month is excluded (grace period). * * @return list */ public function findReducedRatePeriods( Employee $employee, DateTimeImmutable $from, DateTimeImmutable $to ): array { // Look back 13 months to catch maladie that started before the exercise period $extendedFrom = $from->modify('-13 months'); $dates = $this->absenceRepository->findMaladieDatesByEmployee($employee, $extendedFrom, $to); if ([] === $dates) { return []; } $periods = $this->consolidateIntoPeriods($dates); return $this->applyFirstMonthGrace($periods); } /** * Count calendar days in [monthStart, monthEnd] that fall within reduced maladie periods. * * @param list $reducedPeriods */ public function countReducedDaysInMonth( DateTimeImmutable $monthStart, DateTimeImmutable $monthEnd, array $reducedPeriods ): int { $total = 0; foreach ($reducedPeriods as $period) { $overlapStart = $period['start'] > $monthStart ? $period['start'] : $monthStart; $overlapEnd = $period['end'] < $monthEnd ? $period['end'] : $monthEnd; if ($overlapStart > $overlapEnd) { continue; } $total += ((int) $overlapEnd->diff($overlapStart)->format('%a')) + 1; } return $total; } /** * @param list $dates sorted chronologically * * @return list */ private function consolidateIntoPeriods(array $dates): array { $periods = []; $start = $dates[0]; $prev = $start; for ($i = 1, $count = count($dates); $i < $count; ++$i) { $current = $dates[$i]; $gap = (int) $prev->diff($current)->format('%a'); if ($gap > self::MAX_GAP_DAYS) { $periods[] = ['start' => $start, 'end' => $prev]; $start = $current; } $prev = $current; } $periods[] = ['start' => $start, 'end' => $prev]; return $periods; } /** * @param list $periods * * @return list */ private function applyFirstMonthGrace(array $periods): array { $result = []; foreach ($periods as $period) { $gracedStart = $period['start']->modify('+1 month'); if ($gracedStart > $period['end']) { continue; } $result[] = ['start' => $gracedStart, 'end' => $period['end']]; } return $result; } }