validDate($date)) $errors['work_date'] = 'Work date must be a valid date.'; $manual = null; if (array_key_exists('hours', $entry) && $entry['hours'] !== null) { if (!is_numeric($entry['hours'])) { $errors['hours'] = 'Hours must be numeric.'; } else { $manual = (float) $entry['hours']; if ($manual < 0) $errors['hours'] = 'Hours must not be negative.'; } } $start = $entry['start_time'] ?? null; $end = $entry['end_time'] ?? null; if ($manual === null && (($start === null) xor ($end === null))) { $errors['time'] = 'Start and end time must be supplied together.'; } $hours = \calculate_duration_hours($start !== null ? (string)$start : null, $end !== null ? (string)$end : null, $manual); if ($hours === null && !isset($errors['hours']) && !isset($errors['time'])) { $errors['time'] = 'A positive duration or manual hours is required.'; } return ['valid' => $errors === [], 'hours' => $hours, 'errors' => $errors]; } public function isValid(array $entry): bool { return $this->validate($entry)['valid']; } private function validDate(string $date): bool { $parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $date); return $parsed !== false && $parsed->format('Y-m-d') === $date; } }