diff --git a/src/LocalDate.php b/src/LocalDate.php index d22f277..bf43f35 100644 --- a/src/LocalDate.php +++ b/src/LocalDate.php @@ -457,6 +457,50 @@ public function plusDays(int $days) : LocalDate return LocalDate::ofEpochDay($this->toEpochDay() + $days); } + /** + * Returns a copy of this LocalDate with the specified period in weekdays (Monday-Friday) added. + * If the current date is on a weekend and the number of days is zero, the result is the current date. + * This is a slightly different behaviour from PHP DateTime's "+ n weekdays", that would return the next monday. + * + * Note: this is currently a naive implementation that could be greatly improved. + */ + public function plusWeekdays(int $days) : LocalDate + { + $result = $this; + + if ($days < 0) { + $subtractedDays = 0; + + while ($subtractedDays < -$days) { + $result = $result->minusDays(1); + if ($result->getDayOfWeek()->isWeekday()) { + $subtractedDays++; + } + } + } else { + $addedDays = 0; + + while ($addedDays < $days) { + $result = $result->plusDays(1); + if ($result->getDayOfWeek()->isWeekday()) { + $addedDays++; + } + } + } + + return $result; + } + + /** + * Returns a copy of this LocalDate with the specified period in weekdays (Monday-Friday) subtracted. + * If the current date is on a weekend and the number of days is zero, the result is the current date. + * This is a slightly different behaviour from PHP DateTime's "- n weekdays", that would return the next monday. + */ + public function minusWeekdays(int $days) : LocalDate + { + return $this->plusWeekdays(-$days); + } + /** * Returns a copy of this LocalDate with the specified Period subtracted. */ diff --git a/tests/LocalDateTest.php b/tests/LocalDateTest.php index 73e5040..c6b8856 100644 --- a/tests/LocalDateTest.php +++ b/tests/LocalDateTest.php @@ -788,6 +788,184 @@ public function providerPlusDays() : array ]; } + /** + * @dataProvider providerPlusWeekdays + */ + public function testPlusWeekdays(string $date, int $days, string $expectedDate): void + { + $this->assertSame($expectedDate, (string) LocalDate::parse($date)->plusWeekdays($days)); + } + + /** + * @dataProvider providerPlusWeekdays + */ + public function testMinusWeekdays(string $date, int $days, string $expectedDate): void + { + $this->assertSame($expectedDate, (string) LocalDate::parse($date)->minusWeekdays(-$days)); + } + + public function providerPlusWeekdays(): array + { + return [ + ['2020-11-02', -10, '2020-10-19'], + ['2020-11-02', -9, '2020-10-20'], + ['2020-11-02', -8, '2020-10-21'], + ['2020-11-02', -7, '2020-10-22'], + ['2020-11-02', -6, '2020-10-23'], + ['2020-11-02', -5, '2020-10-26'], + ['2020-11-02', -4, '2020-10-27'], + ['2020-11-02', -3, '2020-10-28'], + ['2020-11-02', -2, '2020-10-29'], + ['2020-11-02', -1, '2020-10-30'], + ['2020-11-02', 0, '2020-11-02'], + ['2020-11-02', 1, '2020-11-03'], + ['2020-11-02', 2, '2020-11-04'], + ['2020-11-02', 3, '2020-11-05'], + ['2020-11-02', 4, '2020-11-06'], + ['2020-11-02', 5, '2020-11-09'], + ['2020-11-02', 6, '2020-11-10'], + ['2020-11-02', 7, '2020-11-11'], + ['2020-11-02', 8, '2020-11-12'], + ['2020-11-02', 9, '2020-11-13'], + ['2020-11-02', 10, '2020-11-16'], + + ['2020-11-03', -10, '2020-10-20'], + ['2020-11-03', -9, '2020-10-21'], + ['2020-11-03', -8, '2020-10-22'], + ['2020-11-03', -7, '2020-10-23'], + ['2020-11-03', -6, '2020-10-26'], + ['2020-11-03', -5, '2020-10-27'], + ['2020-11-03', -4, '2020-10-28'], + ['2020-11-03', -3, '2020-10-29'], + ['2020-11-03', -2, '2020-10-30'], + ['2020-11-03', -1, '2020-11-02'], + ['2020-11-03', 0, '2020-11-03'], + ['2020-11-03', 1, '2020-11-04'], + ['2020-11-03', 2, '2020-11-05'], + ['2020-11-03', 3, '2020-11-06'], + ['2020-11-03', 4, '2020-11-09'], + ['2020-11-03', 5, '2020-11-10'], + ['2020-11-03', 6, '2020-11-11'], + ['2020-11-03', 7, '2020-11-12'], + ['2020-11-03', 8, '2020-11-13'], + ['2020-11-03', 9, '2020-11-16'], + ['2020-11-03', 10, '2020-11-17'], + + ['2020-11-04', -10, '2020-10-21'], + ['2020-11-04', -9, '2020-10-22'], + ['2020-11-04', -8, '2020-10-23'], + ['2020-11-04', -7, '2020-10-26'], + ['2020-11-04', -6, '2020-10-27'], + ['2020-11-04', -5, '2020-10-28'], + ['2020-11-04', -4, '2020-10-29'], + ['2020-11-04', -3, '2020-10-30'], + ['2020-11-04', -2, '2020-11-02'], + ['2020-11-04', -1, '2020-11-03'], + ['2020-11-04', 0, '2020-11-04'], + ['2020-11-04', 1, '2020-11-05'], + ['2020-11-04', 2, '2020-11-06'], + ['2020-11-04', 3, '2020-11-09'], + ['2020-11-04', 4, '2020-11-10'], + ['2020-11-04', 5, '2020-11-11'], + ['2020-11-04', 6, '2020-11-12'], + ['2020-11-04', 7, '2020-11-13'], + ['2020-11-04', 8, '2020-11-16'], + ['2020-11-04', 9, '2020-11-17'], + ['2020-11-04', 10, '2020-11-18'], + + ['2020-11-05', -10, '2020-10-22'], + ['2020-11-05', -9, '2020-10-23'], + ['2020-11-05', -8, '2020-10-26'], + ['2020-11-05', -7, '2020-10-27'], + ['2020-11-05', -6, '2020-10-28'], + ['2020-11-05', -5, '2020-10-29'], + ['2020-11-05', -4, '2020-10-30'], + ['2020-11-05', -3, '2020-11-02'], + ['2020-11-05', -2, '2020-11-03'], + ['2020-11-05', -1, '2020-11-04'], + ['2020-11-05', 0, '2020-11-05'], + ['2020-11-05', 1, '2020-11-06'], + ['2020-11-05', 2, '2020-11-09'], + ['2020-11-05', 3, '2020-11-10'], + ['2020-11-05', 4, '2020-11-11'], + ['2020-11-05', 5, '2020-11-12'], + ['2020-11-05', 6, '2020-11-13'], + ['2020-11-05', 7, '2020-11-16'], + ['2020-11-05', 8, '2020-11-17'], + ['2020-11-05', 9, '2020-11-18'], + ['2020-11-05', 10, '2020-11-19'], + + ['2020-11-06', -10, '2020-10-23'], + ['2020-11-06', -9, '2020-10-26'], + ['2020-11-06', -8, '2020-10-27'], + ['2020-11-06', -7, '2020-10-28'], + ['2020-11-06', -6, '2020-10-29'], + ['2020-11-06', -5, '2020-10-30'], + ['2020-11-06', -4, '2020-11-02'], + ['2020-11-06', -3, '2020-11-03'], + ['2020-11-06', -2, '2020-11-04'], + ['2020-11-06', -1, '2020-11-05'], + ['2020-11-06', 0, '2020-11-06'], + ['2020-11-06', 1, '2020-11-09'], + ['2020-11-06', 2, '2020-11-10'], + ['2020-11-06', 3, '2020-11-11'], + ['2020-11-06', 4, '2020-11-12'], + ['2020-11-06', 5, '2020-11-13'], + ['2020-11-06', 6, '2020-11-16'], + ['2020-11-06', 7, '2020-11-17'], + ['2020-11-06', 8, '2020-11-18'], + ['2020-11-06', 9, '2020-11-19'], + ['2020-11-06', 10, '2020-11-20'], + + ['2020-11-07', -10, '2020-10-26'], + ['2020-11-07', -9, '2020-10-27'], + ['2020-11-07', -8, '2020-10-28'], + ['2020-11-07', -7, '2020-10-29'], + ['2020-11-07', -6, '2020-10-30'], + ['2020-11-07', -5, '2020-11-02'], + ['2020-11-07', -4, '2020-11-03'], + ['2020-11-07', -3, '2020-11-04'], + ['2020-11-07', -2, '2020-11-05'], + ['2020-11-07', -1, '2020-11-06'], + ['2020-11-07', 0, '2020-11-07'], + ['2020-11-07', 1, '2020-11-09'], + ['2020-11-07', 2, '2020-11-10'], + ['2020-11-07', 3, '2020-11-11'], + ['2020-11-07', 4, '2020-11-12'], + ['2020-11-07', 5, '2020-11-13'], + ['2020-11-07', 6, '2020-11-16'], + ['2020-11-07', 7, '2020-11-17'], + ['2020-11-07', 8, '2020-11-18'], + ['2020-11-07', 9, '2020-11-19'], + ['2020-11-07', 10, '2020-11-20'], + + ['2020-11-08', -10, '2020-10-26'], + ['2020-11-08', -9, '2020-10-27'], + ['2020-11-08', -8, '2020-10-28'], + ['2020-11-08', -7, '2020-10-29'], + ['2020-11-08', -6, '2020-10-30'], + ['2020-11-08', -5, '2020-11-02'], + ['2020-11-08', -4, '2020-11-03'], + ['2020-11-08', -3, '2020-11-04'], + ['2020-11-08', -2, '2020-11-05'], + ['2020-11-08', -1, '2020-11-06'], + ['2020-11-08', 0, '2020-11-08'], + ['2020-11-08', 1, '2020-11-09'], + ['2020-11-08', 2, '2020-11-10'], + ['2020-11-08', 3, '2020-11-11'], + ['2020-11-08', 4, '2020-11-12'], + ['2020-11-08', 5, '2020-11-13'], + ['2020-11-08', 6, '2020-11-16'], + ['2020-11-08', 7, '2020-11-17'], + ['2020-11-08', 8, '2020-11-18'], + ['2020-11-08', 9, '2020-11-19'], + ['2020-11-08', 10, '2020-11-20'], + + ['2019-01-29', 400, '2020-08-11'], + ['2019-01-29', -400, '2017-07-18'], + ]; + } + /** * @dataProvider providerMinusYears *