Skip to content

Commit

Permalink
feat: add Date.addMonths method (#758)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Georgiev <martin-georgiev@users.noreply.github.com>
Co-authored-by: Ben Challis <ben-challis@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 21, 2024
1 parent a05a9ed commit 4d5062d
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 0 deletions.
28 changes: 28 additions & 0 deletions lib/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ public function isBetween(self $start, self $end): bool
return !$this->isBefore($start) && !$this->isAfter($end);
}

/**
* Returns an instance of {@see Date} incremented by the specified number of months.
* If the resulting month has fewer days than the current day, the day will be the last day of that month.
*
* @throws \InvalidArgumentException if $increment is less than 1
*/
public function addMonths(int $increment): self
{
if ($increment < 1) {
throw new \InvalidArgumentException('Months increment must be greater than 0.');
}

$month = $this->month + $increment;
$year = $this->year;
while ($month > 12) {
$month -= 12;
$year++;
}

// @infection-ignore-all (IncrementInteger)
$daysInNewMonth = (int) DateTimeFactory::immutableFromFormat(
'Y-m-d',
\sprintf('%d-%02d-%02d', $year, $month, 1),
)->format('t');

return new self($year, $month, \min($this->day, $daysInNewMonth));
}

public function offsetByDays(int $days): self
{
if ($days === 0) {
Expand Down
60 changes: 60 additions & 0 deletions tests/unit/DateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,64 @@ public function it_returns_end_of_day_in_provided_timezone(): void
$this->assertSame('2022-05-01 23:59:59.999999', $endOfDay->format('Y-m-d H:i:s.u'));
$this->assertSame('Indian/Mauritius', $endOfDay->getTimezone()->getName());
}

#[Test]
#[DataProvider('provideDatesForMonthIncrement')]
public function it_will_correctly_add_months(
string $currentDate,
int $increment,
string $expectedDate,
): void {
$this->assertSame(
$expectedDate,
Date::fromYearMonthDayString($currentDate)->addMonths($increment)->toYearMonthDayString(),
);
}

/**
* @return iterable<array{string, positive-int, string}>
*/
public static function provideDatesForMonthIncrement(): iterable
{
yield ['2019-01-05', 1, '2019-02-05'];
yield ['2019-12-31', 1, '2020-01-31'];
yield ['2019-01-31', 1, '2019-02-28'];
yield ['2020-01-31', 1, '2020-02-29'];
yield ['2020-03-31', 1, '2020-04-30'];
yield ['2020-11-30', 1, '2020-12-30'];

yield ['2019-01-05', 6, '2019-07-05'];
yield ['2019-10-31', 5, '2020-03-31'];
yield ['2019-08-31', 6, '2020-02-29'];
yield ['2019-10-31', 6, '2020-04-30'];

yield ['2019-01-05', 12, '2020-01-05'];
yield ['2019-10-31', 12, '2020-10-31'];
yield ['2019-08-31', 12, '2020-08-31'];
yield ['2019-10-31', 12, '2020-10-31'];

yield ['2019-01-05', 30, '2021-07-05'];
yield ['2019-10-31', 29, '2022-03-31'];
yield ['2019-08-31', 30, '2022-02-28'];
yield ['2019-10-31', 30, '2022-04-30'];
}

#[Test]
#[DataProvider('provideInvalidDateMonthIncrements')]
public function it_throws_when_incrementing_month_by_less_than_1(int $increment): void
{
$this->expectExceptionObject(new \InvalidArgumentException('Months increment must be greater than 0.'));

Date::fromYearMonthDay(2018, 10, 10)->addMonths($increment);
}

/**
* @return iterable<array{int}>
*/
public static function provideInvalidDateMonthIncrements(): iterable
{
yield [0];
yield [-1];
yield [-10];
}
}

0 comments on commit 4d5062d

Please sign in to comment.