Simple problems require simple solutions
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
2.4 KiB

  1. <?php
  2. namespace App\Services;
  3. use App\DTO\DurationResult;
  4. use App\Enums\DurationModifier;
  5. use App\Traits\Invokable;
  6. use DateInterval;
  7. use DatePeriod;
  8. use DateTime;
  9. use DateTimeImmutable;
  10. use ValueError;
  11. use TypeError;
  12. use Illuminate\Contracts\Container\BindingResolutionException;
  13. use Spatie\LaravelData\Exceptions\CannotCreateData;
  14. use Spatie\LaravelData\Exceptions\CannotSetComputedValue;
  15. /** @package App\Services */
  16. class CalculateDuration
  17. {
  18. const WEEKDAYS = [1, 2, 3, 4, 5];
  19. /**
  20. * @param DateTime $start
  21. * @param DateTime $end
  22. *
  23. * @return void
  24. */
  25. public function __construct(
  26. protected DateTimeImmutable $start,
  27. protected DateTimeImmutable $end
  28. ) {
  29. }
  30. /**
  31. * @param DurationModifier $durationModifier
  32. *
  33. * @return DurationResult
  34. *
  35. * @throws ValueError
  36. * @throws TypeError
  37. * @throws BindingResolutionException
  38. * @throws CannotCreateData
  39. * @throws CannotSetComputedValue
  40. */
  41. public function result(DurationModifier $durationModifier = DurationModifier::None): DurationResult
  42. {
  43. $days = $this->calculateDuration();
  44. $weekDays = $this->calculateDuration(true);
  45. $weeks = $this->calculateDurationWeeks();
  46. $durationModifierInDays = $durationModifier->durationModiferInDays();
  47. return new DurationResult(
  48. days: $days * $durationModifierInDays,
  49. weeks: $weeks * ($durationModifier === DurationModifier::None ? 1 : $durationModifierInDays * 7),
  50. weekDays: $weekDays * $durationModifierInDays,
  51. );
  52. }
  53. /**
  54. * @param string $intervalDuration
  55. * @param bool $skipWeekends
  56. *
  57. * @return int
  58. */
  59. protected function calculateDuration(bool $skipWeekends = false): int
  60. {
  61. $interval = new DateInterval('P1D');
  62. $period = new DatePeriod(
  63. $this->start,
  64. $interval,
  65. $this->end->add($interval),
  66. );
  67. $intervalCount = 0;
  68. foreach ($period as $date) {
  69. if ($skipWeekends && !in_array($date->format('N'), self::WEEKDAYS)) {
  70. continue;
  71. }
  72. $intervalCount++;
  73. }
  74. return $intervalCount;
  75. }
  76. /**
  77. * @return int
  78. */
  79. protected function calculateDurationWeeks(): int
  80. {
  81. return floor($this->start->diff($this->end)->days / 7);
  82. }
  83. }