$days keyed by Y-m-d */ private function __construct(private readonly array $days) { } public static function fromArray(array $days): self { $clean = []; foreach ($days as $key => $value) { if (!is_string($key) || !is_array($value) || 1 !== preg_match('/^\d{4}-\d{2}-\d{2}$/', $key)) { continue; } $entry = ['minutes' => (int) ($value['minutes'] ?? 0)]; if (0 < $break = (int) ($value['break'] ?? 0)) { $entry['break'] = $break; } $start = self::cleanTime($value['start'] ?? null); $end = self::cleanTime($value['end'] ?? null); // A span needs both ends; half of one says nothing. if (null !== $start && null !== $end) { $entry['start'] = $start; $entry['end'] = $end; } $absence = $value['absence'] ?? null; if (is_string($absence) && '' !== $absence) { $entry['absence'] = $absence; } $clean[$key] = $entry; } ksort($clean); return new self($clean); } public static function fromTimeAccount(TimeAccount $account): self { $days = []; foreach ($account->getDays() as $key => $day) { /* @var TimeAccountDay $day */ $days[$key] = ['minutes' => $day->getMinutes()]; if (0 < $day->getBreakMinutes()) { $days[$key]['break'] = $day->getBreakMinutes(); } if (null !== $day->getStartTime() && null !== $day->getEndTime()) { $days[$key]['start'] = $day->getStartTime(); $days[$key]['end'] = $day->getEndTime(); } if (null !== $day->getHolidayType()) { $days[$key]['absence'] = $day->getHolidayType(); } } return new self($days); } private static function cleanTime(mixed $raw): ?string { return is_string($raw) && 1 === preg_match('/^\d{2}:\d{2}$/', $raw) ? $raw : null; } public function toArray(): array { return $this->days; } public function getTotalMinutes(): int { return array_sum(array_column($this->days, 'minutes')); } public function countWorkedDays(): int { return count(array_filter($this->days, static fn (array $day) => ($day['minutes'] ?? 0) > 0)); } public function countAbsenceDays(): int { return count(array_filter($this->days, static fn (array $day) => isset($day['absence']))); } public function getTotalBreakMinutes(): int { return array_sum(array_map(static fn (array $day) => $day['break'] ?? 0, $this->days)); } /** * Every day of the month in order, reported or not, for tables and the PDF. * * @return list */ public function forCalendarMonth(int $year, int $month): array { $first = \DateTimeImmutable::createFromFormat('!Y-n-j', sprintf('%04d-%d-1', $year, $month)); if (false === $first) { return []; } $days = []; for ($day = 1, $last = (int) $first->format('t'); $day <= $last; ++$day) { $date = $first->setDate($year, $month, $day); $entry = $this->days[$date->format('Y-m-d')] ?? null; $days[] = new WorkMonthDay( $date, null !== $entry ? (int) $entry['minutes'] : null, $entry['absence'] ?? null, $entry['start'] ?? null, $entry['end'] ?? null, (int) ($entry['break'] ?? 0), ); } return $days; } }