<?php namespace App\Services; use App\DTO\DurationResult; use App\Enums\DurationModifier; use App\Traits\Invokable; use DateInterval; use DatePeriod; use DateTime; use DateTimeImmutable; use ValueError; use TypeError; use Illuminate\Contracts\Container\BindingResolutionException; use Spatie\LaravelData\Exceptions\CannotCreateData; use Spatie\LaravelData\Exceptions\CannotSetComputedValue; /** @package App\Services */ class CalculateDuration { const WEEKDAYS = [1, 2, 3, 4, 5]; /** * @param DateTime $start * @param DateTime $end * * @return void */ public function __construct( protected DateTimeImmutable $start, protected DateTimeImmutable $end ) { } /** * @param DurationModifier $durationModifier * * @return DurationResult * * @throws ValueError * @throws TypeError * @throws BindingResolutionException * @throws CannotCreateData * @throws CannotSetComputedValue */ public function result(DurationModifier $durationModifier = DurationModifier::None): DurationResult { $days = $this->calculateDuration(); $weekDays = $this->calculateDuration(true); $weeks = $this->calculateDurationWeeks(); $durationModifierInDays = $durationModifier->durationModiferInDays(); return new DurationResult( days: $days * $durationModifierInDays, weeks: $weeks * ($durationModifier === DurationModifier::None ? 1 : $durationModifierInDays * 7), weekDays: $weekDays * $durationModifierInDays, ); } /** * @param string $intervalDuration * @param bool $skipWeekends * * @return int */ protected function calculateDuration(bool $skipWeekends = false): int { $interval = new DateInterval('P1D'); $period = new DatePeriod( $this->start, $interval, $this->end->add($interval), ); $intervalCount = 0; foreach ($period as $date) { if ($skipWeekends && !in_array($date->format('N'), self::WEEKDAYS)) { continue; } $intervalCount++; } return $intervalCount; } /** * @return int */ protected function calculateDurationWeeks(): int { return floor($this->start->diff($this->end)->days / 7); } }