From ef937530af4946c8ef1a5ee97ca05d24def69346 Mon Sep 17 00:00:00 2001 From: Ondrej Vlach Date: Wed, 7 Aug 2024 12:53:46 +0200 Subject: [PATCH] feast(services): add duration solver --- app/Services/WorkingDays/DurationSolver.php | 55 ++++++++++++++++ .../WorkingDays/DurationSolverFactory.php | 29 +++++++++ .../WorkingDays/DurationSolverTest.php | 64 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 app/Services/WorkingDays/DurationSolver.php create mode 100644 app/Services/WorkingDays/DurationSolverFactory.php create mode 100644 tests/Unit/Services/WorkingDays/DurationSolverTest.php diff --git a/app/Services/WorkingDays/DurationSolver.php b/app/Services/WorkingDays/DurationSolver.php new file mode 100644 index 0000000..1a6a61f --- /dev/null +++ b/app/Services/WorkingDays/DurationSolver.php @@ -0,0 +1,55 @@ +setTime(0, 0)->diff($endDate->setTime(0, 0))->format('%a')) + 1; + + do { + $workingDaysCount = (int) ($startDate->setTime(0, 0)->diff($endDate->setTime(0, 0))->format('%a')) + 1; + $workingDaysCountInInterval = $this->workingDayDeterminer->getWorkingDaysCount($startDate, $workingDaysCount); + + if ($workingDaysCountInInterval >= $requiredWorkingDaysCount) { + // there is no weekends or holidays in the given interval + break; + } else { + $endDate = $endDate->add(new \DateInterval(sprintf('P%dD', $requiredWorkingDaysCount - $workingDaysCountInInterval))); + if ($loopId > static::MAX_ITERATIONS) { + throw new \InvalidArgumentException('No working days found in the given interval'); + } + } + + $loopId++; + } while ($workingDaysCount != $workingDaysCountInInterval); + + return $endDate; + } +} diff --git a/app/Services/WorkingDays/DurationSolverFactory.php b/app/Services/WorkingDays/DurationSolverFactory.php new file mode 100644 index 0000000..9c35330 --- /dev/null +++ b/app/Services/WorkingDays/DurationSolverFactory.php @@ -0,0 +1,29 @@ +workingDayDeterminerFactory->createForCountry($countryCode), + $this->durationConvertor, + ); + } +} diff --git a/tests/Unit/Services/WorkingDays/DurationSolverTest.php b/tests/Unit/Services/WorkingDays/DurationSolverTest.php new file mode 100644 index 0000000..478a37a --- /dev/null +++ b/tests/Unit/Services/WorkingDays/DurationSolverTest.php @@ -0,0 +1,64 @@ +>> + */ + public static function solverDurationProvider(): array + { + // 4 days is required state + return [ + [3, [4]], /* without holidays */ + [4, [3, 4]], /* 1 day */ + [6, [2, 3, 4]], /* 1 weekend, monday holiday */ + [5, [2, 4]], /* 1 weekend */ + ]; + } + + /** + * @param int $expectedResult + * @param array $solverResults + * @return void + * @throws \PHPUnit\Framework\MockObject\Exception + */ + #[DataProvider('solverDurationProvider')] + public function testSolverWillSolveDuration(int $expectedResult, array $solverResults): void + { + $workingDayDeterminer = $this->createMock(WorkingDayDeterminerInterface::class); + $workingDayDeterminer->method('getWorkingDaysCount')->willReturnCallback(function () use ($solverResults) { + static $calledCount = 0; + return $solverResults[$calledCount++] ?? throw new \Exception("Not enough solver results for try $calledCount" . print_r($solverResults, true)); + }); + $durationSolver = new DurationSolver( + $workingDayDeterminer, + new DurationConvertor() + ); + $this->assertEquals( + (new \DateTimeImmutable('2022-01-01 23:59:59'))->add(new \DateInterval(sprintf('P%dD', $expectedResult))), + $durationSolver->calculateDuration(new \DateTimeImmutable('2022-01-01 00:00:00'), new \DateTimeImmutable('2022-01-04 23:59:59')) + ); + } + + public function testDurationSolverWillThrowExceptionWhenCantSolveDuration(): void + { + $workingDayDeterminer = $this->createMock(WorkingDayDeterminerInterface::class); + $workingDayDeterminer->method('getWorkingDaysCount')->willReturn(0); + $durationSolver = new DurationSolver( + $workingDayDeterminer, + new DurationConvertor() + ); + $this->expectException(\InvalidArgumentException::class); + $durationSolver->calculateDuration(new \DateTimeImmutable('2022-01-01 00:00:00'), new \DateTimeImmutable('2022-01-04 23:59:59')); + } +}