app/classes/ReleaseInsights/Duration.php (58 lines of code) (raw):

<?php declare(strict_types=1); namespace ReleaseInsights; use DateInterval, DatePeriod, DateTime; /** * Calculate durations in days and working days to indicate working days left in schedules */ class Duration { /** @var array<string> $wellness_days */ private readonly array $wellness_days; public function __construct( public readonly Datetime $start, public readonly Datetime $end ) { $this->wellness_days = include DATA . 'wellness_days.php'; } /** * Get The number of days between 2 dates */ public function days(): int { return $this->start->diff($this->end)->days; } /** * Get The number of weeks between 2 dates * We round down to 0.5 */ public function weeks(): float { // We are limiting to 1 decimal for the week $weeks = number_format($this->days()/7, 1); // We want some logic to round down only the decimal to 0.5 [$main, $minor] = explode('.', (string) $weeks); $minor = $minor >= 5 ? 5 : 0; return (float) ($main . '.' . $minor); } /** * Check if a day is a workday */ public function isWorkDay(DateTime $day): bool { // We don't consider the current day as a working day if ($day->format('Y-m-d') === new DateTime()->format('Y-m-d')) { return false; } // We substract week-end days if (in_array($day->format('l'), ['Saturday','Sunday'])) { return false; } // We substract wellness days if (in_array($day->format('Y-m-d'), $this->wellness_days)) { return false; } return true; } /** * Get The number of working days between 2 dates */ public function workDays(): int { $count = 0; // P1D is short for 'Period: 1 Day' $range = new DatePeriod(start: $this->start, end: $this->end, interval: new DateInterval('P1D')); foreach($range as $date){ if ($this->isWorkDay($date)) { $count++; } } return $count; } /** * Return all the data in an array for template use * * @return array<string, float|int> */ public function report(): array { return [ 'days' => $this->days(), 'workdays' => $this->workDays(), 'weeks' => $this->weeks(), ]; } }