Skip to content

Commit

Permalink
Added Date->datesUntil, Year->yearsUntil and Year-modify (#21)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Kolb <info@digital-craftsman.de>
  • Loading branch information
christian-kolb and Christian Kolb authored Jul 19, 2023
1 parent 82104d1 commit 5124ee0
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.7.0

- Added `Date->datesUntil(Date $date, PeriodLimit $periodLimit = PeriodLimit::INCLUDING_START_AND_END): array`.
- Added `Year->yearsUntil(Year $year, PeriodLimit $periodLimit = PeriodLimit::INCLUDING_START_AND_END): array`.
- Added `Year->modify(string $modifier): Year`.

## 0.6.1

- Added missing `DateTimeType->requiresSQLCommentHint(): bool`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Install package through composer:
composer require digital-craftsman/datetime-parts
```

> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/datetime-parts:0.6.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/datetime-parts:0.7.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
## When would I need that?

Expand Down
4 changes: 4 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Upgrade guide

## From 0.6.* to 0.7.0

No breaking changes

## From 0.5.* to 0.6.0

No breaking changes
Expand Down
38 changes: 38 additions & 0 deletions src/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,44 @@ public function compareTo(self $date): int
return $this->toDateTimeImmutable() <=> $date->toDateTimeImmutable();
}

/**
* Returns all dates until the given date. If the given date is before this date, the result will be an empty array.
*
* @return array<int, Date>
*/
public function datesUntil(
self $date,
PeriodLimit $periodLimit = PeriodLimit::INCLUDING_START_AND_END,
): array {
$startDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_START
? $this
->modify('- 1 day')
->toDateTimeImmutable()
: $this->toDateTimeImmutable();

$endDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_END
? $date
->modify('+ 1 day')
->toDateTimeImmutable()
: $date->toDateTimeImmutable();

$interval = new \DateInterval('P1D');
/**
* The options here seem counter-intuitive, but are set in a way that this logic is only handled in one place (above) instead of
* two place with part of it above and part below.
*/
$period = new \DatePeriod($startDateTime, $interval, $endDateTime, \DatePeriod::EXCLUDE_START_DATE);

$dates = [];
foreach ($period as $dateTime) {
$dates[] = self::fromDateTime($dateTime);
}

return $dates;
}

// Mutations

public function format(string $format): string
Expand Down
6 changes: 3 additions & 3 deletions src/Month.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ public function monthsUntil(
$interval = new \DateInterval('P1M');
/**
* The options here seem counter-intuitive, but are set in a way that this logic is only handled in one place (above) instead of
* two place with part of it above and part below. With PHP 8.2 there is a nicer way with an additional flag.
* two place with part of it above and part below.
*/
$period = new \DatePeriod($startDateTime, $interval, $endDateTime, \DatePeriod::EXCLUDE_START_DATE);

$months = [];
foreach ($period as $date) {
$months[] = self::fromDateTime($date);
foreach ($period as $dateTime) {
$months[] = self::fromDateTime($dateTime);
}

return $months;
Expand Down
47 changes: 47 additions & 0 deletions src/Year.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,44 @@ public function compareTo(self $year): int
return $this->toDateTimeImmutable() <=> $year->toDateTimeImmutable();
}

/**
* Returns all years until the given year. If the given year is before this year, the result will be an empty array.
*
* @return array<int, Year>
*/
public function yearsUntil(
self $year,
PeriodLimit $periodLimit = PeriodLimit::INCLUDING_START_AND_END,
): array {
$startDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_START
? $this
->modify('- 1 year')
->toDateTimeImmutable()
: $this->toDateTimeImmutable();

$endDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_END
? $year
->modify('+ 1 year')
->toDateTimeImmutable()
: $year->toDateTimeImmutable();

$interval = new \DateInterval('P1Y');
/**
* The options here seem counter-intuitive, but are set in a way that this logic is only handled in one place (above) instead of
* two place with part of it above and part below.
*/
$period = new \DatePeriod($startDateTime, $interval, $endDateTime, \DatePeriod::EXCLUDE_START_DATE);

$years = [];
foreach ($period as $dateTime) {
$years[] = self::fromDateTime($dateTime);
}

return $years;
}

// -- Mutations

public function format(string $format): string
Expand All @@ -78,6 +116,15 @@ public function format(string $format): string
->format($format);
}

public function modify(string $modifier): self
{
$modifiedDateTime = $this->toDateTimeImmutable()
->modify($modifier);

/** @psalm-suppress PossiblyFalseArgument */
return self::fromDateTime($modifiedDateTime);
}

public function toDateTimeInTimeZone(\DateTimeZone $timeZone): DateTime
{
return DateTime::fromStringInTimeZone(
Expand Down
94 changes: 94 additions & 0 deletions tests/DateDatesUntilTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\DateTimeParts;

use PHPUnit\Framework\TestCase;

/** @coversDefaultClass \DigitalCraftsman\DateTimeParts\Date */
final class DateDatesUntilTest extends TestCase
{
/**
* @test
*
* @dataProvider dataProvider
*
* @covers ::datesUntil
*/
public function dates_until_works(
array $expectedResult,
Date $startDate,
Date $endDate,
PeriodLimit $periodLimit,
): void {
// -- Act & Assert
self::assertEquals($expectedResult, $startDate->datesUntil($endDate, $periodLimit));
}

/**
* @return array<string, array{
* 0: array<int, Date>,
* 1: Date,
* 2: Date,
* 3: PeriodLimit,
* }>
*/
public function dataProvider(): array
{
return [
'two days apart with start and end included' => [
[
Date::fromString('2022-08-03'),
Date::fromString('2022-08-04'),
Date::fromString('2022-08-05'),
],
Date::fromString('2022-08-03'),
Date::fromString('2022-08-05'),
PeriodLimit::INCLUDING_START_AND_END,
],
'three days apart with start and end included over the ends of a year' => [
[
Date::fromString('2022-12-30'),
Date::fromString('2022-12-31'),
Date::fromString('2023-01-01'),
],
Date::fromString('2022-12-30'),
Date::fromString('2023-01-01'),
PeriodLimit::INCLUDING_START_AND_END,
],
'two days apart with start included' => [
[
Date::fromString('2022-09-08'),
Date::fromString('2022-09-09'),
],
Date::fromString('2022-09-08'),
Date::fromString('2022-09-10'),
PeriodLimit::INCLUDING_START,
],
'two days apart with end included' => [
[
Date::fromString('2022-09-09'),
Date::fromString('2022-09-10'),
],
Date::fromString('2022-09-08'),
Date::fromString('2022-09-10'),
PeriodLimit::INCLUDING_END,
],
'two days apart with start and end excluded' => [
[
Date::fromString('2022-09-09'),
],
Date::fromString('2022-09-08'),
Date::fromString('2022-09-10'),
PeriodLimit::EXCLUDING_START_AND_END,
],
'two days apart with start after end' => [
[],
Date::fromString('2022-09-11'),
Date::fromString('2022-09-09'),
PeriodLimit::INCLUDING_START,
],
];
}
}
55 changes: 55 additions & 0 deletions tests/YearModifyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\DateTimeParts;

use PHPUnit\Framework\TestCase;

/** @coversDefaultClass \DigitalCraftsman\DateTimeParts\Year */
final class YearModifyTest extends TestCase
{
/**
* @test
*
* @dataProvider dataProvider
*
* @covers ::modify
*/
public function modify_works(
Year $expectedResult,
Year $month,
string $modifier,
): void {
// -- Act & Assert
self::assertEquals($expectedResult, $month->modify($modifier));
}

/**
* @return array<string, array{
* 0: Year,
* 1: Year,
* 2: string,
* }>
*/
public function dataProvider(): array
{
return [
'subtract one year' => [
Year::fromString('2021'),
Year::fromString('2022'),
'- 1 year',
],
'add one year' => [
Year::fromString('2023'),
Year::fromString('2022'),
'+ 1 year',
],
'stupid but valid modification with one day' => [
Year::fromString('2022'),
Year::fromString('2022'),
'+ 1 day',
],
];
}
}
84 changes: 84 additions & 0 deletions tests/YearYearsUntilTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\DateTimeParts;

use PHPUnit\Framework\TestCase;

/** @coversDefaultClass \DigitalCraftsman\DateTimeParts\Year */
final class YearYearsUntilTest extends TestCase
{
/**
* @test
*
* @dataProvider dataProvider
*
* @covers ::yearsUntil
*/
public function years_until_works(
array $expectedResult,
Year $startYear,
Year $endYear,
PeriodLimit $periodLimit,
): void {
// -- Act & Assert
self::assertEquals($expectedResult, $startYear->yearsUntil($endYear, $periodLimit));
}

/**
* @return array<string, array{
* 0: array<int, Year>,
* 1: Year,
* 2: Year,
* 3: PeriodLimit,
* }>
*/
public function dataProvider(): array
{
return [
'two years apart with start and end included' => [
[
Year::fromString('2021'),
Year::fromString('2022'),
Year::fromString('2023'),
],
Year::fromString('2021'),
Year::fromString('2023'),
PeriodLimit::INCLUDING_START_AND_END,
],
'two years apart with start included' => [
[
Year::fromString('2022'),
Year::fromString('2023'),
],
Year::fromString('2022'),
Year::fromString('2024'),
PeriodLimit::INCLUDING_START,
],
'two years apart with end included' => [
[
Year::fromString('2023'),
Year::fromString('2024'),
],
Year::fromString('2022'),
Year::fromString('2024'),
PeriodLimit::INCLUDING_END,
],
'two years apart with start and end excluded' => [
[
Year::fromString('2023'),
],
Year::fromString('2022'),
Year::fromString('2024'),
PeriodLimit::EXCLUDING_START_AND_END,
],
'two years apart with start after end' => [
[],
Year::fromString('2024'),
Year::fromString('2022'),
PeriodLimit::INCLUDING_START,
],
];
}
}

0 comments on commit 5124ee0

Please sign in to comment.