diff --git a/examples/DateTime/TestLocalDateRange.php b/examples/DateTime/TestLocalDateRange.php index f2d6ee6..07b646d 100644 --- a/examples/DateTime/TestLocalDateRange.php +++ b/examples/DateTime/TestLocalDateRange.php @@ -6,7 +6,9 @@ use WizDevelop\PhpValueObject\DateTime\LocalDate; use WizDevelop\PhpValueObject\DateTime\LocalDateRange; -use WizDevelop\PhpValueObject\DateTime\RangeType; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeClosed; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeHalfOpenRight; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeOpen; require_once __DIR__ . '/../../vendor/autoload.php'; @@ -21,15 +23,13 @@ // 2. 開区間と閉区間の違い echo "=== 開区間と閉区間の違い ===\n"; -$closedWeek = LocalDateRange::from( +$closedWeek = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 7), - RangeType::CLOSED, ); -$openWeek = LocalDateRange::from( +$openWeek = LocalDateRangeOpen::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 7), - RangeType::OPEN, ); echo "閉区間(両端含む): {$closedWeek->toISOString()} = {$closedWeek->days()} 日\n"; @@ -38,10 +38,9 @@ // 3. 半開区間の使用例(一般的な日付範囲の表現) echo "=== 半開区間の使用例 ===\n"; // 月初から月末まで(月末を含まない一般的なパターン) -$month = LocalDateRange::from( +$month = LocalDateRangeHalfOpenRight::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 2, 1), - RangeType::HALF_OPEN_RIGHT, ); echo "1月(右半開区間): {$month->toISOString()}\n"; @@ -50,10 +49,9 @@ // 4. 日付の反復処理 echo "=== 日付の反復処理 ===\n"; -$weekRange = LocalDateRange::from( +$weekRange = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 7), - RangeType::CLOSED, ); echo "1週間の日付:\n"; @@ -64,20 +62,17 @@ // 5. 期間の重なり判定 echo "=== 期間の重なり判定 ===\n"; -$q1 = LocalDateRange::from( +$q1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 3, 31), - RangeType::CLOSED, ); -$q2 = LocalDateRange::from( +$q2 = LocalDateRangeClosed::from( LocalDate::of(2024, 4, 1), LocalDate::of(2024, 6, 30), - RangeType::CLOSED, ); -$marchToMay = LocalDateRange::from( +$marchToMay = LocalDateRangeClosed::from( LocalDate::of(2024, 3, 1), LocalDate::of(2024, 5, 31), - RangeType::CLOSED, ); echo "第1四半期: {$q1->toISOString()}\n"; @@ -100,10 +95,9 @@ // 7. エラーハンドリング echo "=== エラーハンドリング ===\n"; -$invalidResult = LocalDateRange::tryFrom( +$invalidResult = LocalDateRangeClosed::tryFrom( LocalDate::of(2024, 12, 31), LocalDate::of(2024, 1, 1), - RangeType::CLOSED, ); if ($invalidResult->isErr()) { @@ -119,10 +113,9 @@ // 9. 年間カレンダーの例 echo "\n=== 年間カレンダーの例 ===\n"; -$year2024 = LocalDateRange::from( +$year2024 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 12, 31), - RangeType::CLOSED, ); echo "2024年: {$year2024->toISOString()}\n"; diff --git a/src/DateTime/LocalDateRange.php b/src/DateTime/LocalDateRange.php index c50af94..b98456c 100644 --- a/src/DateTime/LocalDateRange.php +++ b/src/DateTime/LocalDateRange.php @@ -23,6 +23,14 @@ */ readonly class LocalDateRange implements IValueObject, Stringable, IteratorAggregate, Countable { + /** + * NOTE: 範囲種別(子クラスでオーバーライド可能) + */ + public static function rangeType(): RangeType + { + return RangeType::HALF_OPEN_RIGHT; // デフォルトの範囲タイプ + } + /** * Avoid new() operator. * @@ -32,7 +40,6 @@ final private function __construct( private mixed $from, private mixed $to, - private RangeType $rangeType ) { // NOTE: 不変条件(invariant) assert(static::isValid($from, $to)->isOk()); @@ -46,7 +53,7 @@ final public function equals(IValueObject $other): bool { return $this->from->equals($other->from) && $this->to->equals($other->to) - && $this->rangeType === $other->rangeType; + && static::rangeType() === $other->rangeType(); } #[Override] @@ -75,9 +82,8 @@ final public function jsonSerialize(): string final public static function from( mixed $from, mixed $to, - RangeType $rangeType = RangeType::HALF_OPEN_RIGHT ): static { - return new static($from, $to, $rangeType); + return new static($from, $to); } /** @@ -89,10 +95,9 @@ final public static function from( final public static function tryFrom( mixed $from, mixed $to, - RangeType $rangeType = RangeType::HALF_OPEN_RIGHT ): Result { return static::isValid($from, $to) - ->andThen(static fn () => Result\ok(static::from($from, $to, $rangeType))); + ->andThen(static fn () => Result\ok(static::from($from, $to))); } // ------------------------------------------------------------------------- @@ -126,12 +131,12 @@ protected static function isValid(mixed $from, mixed $to): Result */ final public function toISOString(): string { - $leftBracket = match ($this->rangeType) { + $leftBracket = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_RIGHT => '[', RangeType::OPEN, RangeType::HALF_OPEN_LEFT => '(', }; - $rightBracket = match ($this->rangeType) { + $rightBracket = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_LEFT => ']', RangeType::OPEN, RangeType::HALF_OPEN_RIGHT => ')', }; @@ -155,9 +160,26 @@ final public function getTo(): mixed return $this->to; } - final public function getRangeType(): RangeType + /** + * @return TStart + */ + final public function getFromAsClosed(): mixed + { + return match (static::rangeType()) { + RangeType::CLOSED, RangeType::HALF_OPEN_RIGHT => $this->from, + RangeType::OPEN, RangeType::HALF_OPEN_LEFT => $this->from->addDays(1), + }; + } + + /** + * @return TEnd + */ + final public function getToAsClosed(): mixed { - return $this->rangeType; + return match (static::rangeType()) { + RangeType::CLOSED, RangeType::HALF_OPEN_LEFT => $this->to, + RangeType::OPEN, RangeType::HALF_OPEN_RIGHT => $this->to->addDays(-1), + }; } /** @@ -167,7 +189,7 @@ final public function getRangeType(): RangeType */ final public function withFrom(mixed $from): static { - return static::from($from, $this->to, $this->rangeType); + return static::from($from, $this->to); } /** @@ -177,7 +199,7 @@ final public function withFrom(mixed $from): static */ final public function withTo(mixed $to): static { - return static::from($this->from, $to, $this->rangeType); + return static::from($this->from, $to); } /** @@ -187,7 +209,7 @@ final public function withTo(mixed $to): static */ final public function tryWithFrom(mixed $from): Result { - return static::tryFrom($from, $this->to, $this->rangeType); + return static::tryFrom($from, $this->to); } /** @@ -197,7 +219,7 @@ final public function tryWithFrom(mixed $from): Result */ final public function tryWithTo(mixed $to): Result { - return static::tryFrom($this->from, $to, $this->rangeType); + return static::tryFrom($this->from, $to); } /** @@ -205,12 +227,12 @@ final public function tryWithTo(mixed $to): Result */ final public function contains(LocalDate $date): bool { - $afterFrom = match ($this->rangeType) { + $afterFrom = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_RIGHT => $date->isAfterOrEqualTo($this->from), RangeType::OPEN, RangeType::HALF_OPEN_LEFT => $date->isAfter($this->from), }; - $beforeTo = match ($this->rangeType) { + $beforeTo = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_LEFT => $date->isBeforeOrEqualTo($this->to), RangeType::OPEN, RangeType::HALF_OPEN_RIGHT => $date->isBefore($this->to), }; @@ -240,10 +262,10 @@ final public function strictlyBefore(self $other): bool { return $this->to->isBefore($other->from) || ( $this->to->is($other->from) && ( - $this->rangeType === RangeType::OPEN - || $this->rangeType === RangeType::HALF_OPEN_RIGHT - || $other->rangeType === RangeType::OPEN - || $other->rangeType === RangeType::HALF_OPEN_LEFT + static::rangeType() === RangeType::OPEN + || static::rangeType() === RangeType::HALF_OPEN_RIGHT + || $other->rangeType() === RangeType::OPEN + || $other->rangeType() === RangeType::HALF_OPEN_LEFT ) ); } @@ -260,7 +282,7 @@ final public function days(): int $days = (int)(($toTimestamp - $fromTimestamp) / 86400); // 区間タイプによる調整 - return match ($this->rangeType) { + return match (static::rangeType()) { RangeType::CLOSED => $days + 1, // 両端を含む RangeType::OPEN => max(0, $days - 1), // 両端を含まない RangeType::HALF_OPEN_LEFT, RangeType::HALF_OPEN_RIGHT => $days, // 片方の端を含む @@ -274,12 +296,12 @@ final public function days(): int #[Override] final public function getIterator(): Generator { - $current = match ($this->rangeType) { + $current = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_RIGHT => $this->from, RangeType::OPEN, RangeType::HALF_OPEN_LEFT => $this->from->addDays(1), }; - $endCondition = match ($this->rangeType) { + $endCondition = match (static::rangeType()) { RangeType::CLOSED, RangeType::HALF_OPEN_LEFT => fn (LocalDate $date) => $date->isBeforeOrEqualTo($this->to), RangeType::OPEN, RangeType::HALF_OPEN_RIGHT => fn (LocalDate $date) => $date->isBefore($this->to), }; diff --git a/src/DateTime/LocalDateRange/LocalDateRangeClosed.php b/src/DateTime/LocalDateRange/LocalDateRangeClosed.php new file mode 100644 index 0000000..f3f1316 --- /dev/null +++ b/src/DateTime/LocalDateRange/LocalDateRangeClosed.php @@ -0,0 +1,22 @@ + + */ +final readonly class LocalDateRangeClosed extends LocalDateRange +{ + #[Override] + public static function rangeType(): RangeType + { + return RangeType::CLOSED; + } +} diff --git a/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenLeft.php b/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenLeft.php new file mode 100644 index 0000000..15a7a66 --- /dev/null +++ b/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenLeft.php @@ -0,0 +1,22 @@ + + */ +final readonly class LocalDateRangeHalfOpenLeft extends LocalDateRange +{ + #[Override] + public static function rangeType(): RangeType + { + return RangeType::HALF_OPEN_LEFT; + } +} diff --git a/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenRight.php b/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenRight.php new file mode 100644 index 0000000..35261fc --- /dev/null +++ b/src/DateTime/LocalDateRange/LocalDateRangeHalfOpenRight.php @@ -0,0 +1,22 @@ + + */ +final readonly class LocalDateRangeHalfOpenRight extends LocalDateRange +{ + #[Override] + public static function rangeType(): RangeType + { + return RangeType::HALF_OPEN_RIGHT; + } +} diff --git a/src/DateTime/LocalDateRange/LocalDateRangeOpen.php b/src/DateTime/LocalDateRange/LocalDateRangeOpen.php new file mode 100644 index 0000000..e8558ca --- /dev/null +++ b/src/DateTime/LocalDateRange/LocalDateRangeOpen.php @@ -0,0 +1,22 @@ + + */ +final readonly class LocalDateRangeOpen extends LocalDateRange +{ + #[Override] + public static function rangeType(): RangeType + { + return RangeType::OPEN; + } +} diff --git a/tests/Unit/DateTime/LocalDateRangeTest.php b/tests/Unit/DateTime/LocalDateRangeTest.php index b4db2b9..d6988a0 100644 --- a/tests/Unit/DateTime/LocalDateRangeTest.php +++ b/tests/Unit/DateTime/LocalDateRangeTest.php @@ -8,6 +8,10 @@ use PHPUnit\Framework\TestCase; use WizDevelop\PhpValueObject\DateTime\LocalDate; use WizDevelop\PhpValueObject\DateTime\LocalDateRange; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeClosed; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeHalfOpenLeft; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeHalfOpenRight; +use WizDevelop\PhpValueObject\DateTime\LocalDateRange\LocalDateRangeOpen; use WizDevelop\PhpValueObject\DateTime\RangeType; use WizDevelop\PhpValueObject\Error\ValueObjectError; @@ -34,12 +38,12 @@ public function test_閉区間で有効な範囲を作成できる(): void $to = LocalDate::of(2024, 1, 31); // Act - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Assert $this->assertSame($from, $range->getFrom()); $this->assertSame($to, $range->getTo()); - $this->assertSame(RangeType::CLOSED, $range->getRangeType()); + $this->assertSame(RangeType::CLOSED, $range->rangeType()); $this->assertSame('[2024-01-01, 2024-01-31]', $range->toISOString()); } @@ -50,10 +54,10 @@ public function test_開区間で有効な範囲を作成できる(): void $to = LocalDate::of(2024, 1, 31); // Act - $range = LocalDateRange::from($from, $to, RangeType::OPEN); + $range = LocalDateRangeOpen::from($from, $to); // Assert - $this->assertSame(RangeType::OPEN, $range->getRangeType()); + $this->assertSame(RangeType::OPEN, $range->rangeType()); $this->assertSame('(2024-01-01, 2024-01-31)', $range->toISOString()); } @@ -64,13 +68,13 @@ public function test_半開区間で有効な範囲を作成できる(): void $to = LocalDate::of(2024, 1, 31); // Act - $rangeLeft = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_LEFT); - $rangeRight = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_RIGHT); + $rangeLeft = LocalDateRangeHalfOpenLeft::from($from, $to); + $rangeRight = LocalDateRangeHalfOpenRight::from($from, $to); // Assert - $this->assertSame(RangeType::HALF_OPEN_LEFT, $rangeLeft->getRangeType()); + $this->assertSame(RangeType::HALF_OPEN_LEFT, $rangeLeft->rangeType()); $this->assertSame('(2024-01-01, 2024-01-31]', $rangeLeft->toISOString()); - $this->assertSame(RangeType::HALF_OPEN_RIGHT, $rangeRight->getRangeType()); + $this->assertSame(RangeType::HALF_OPEN_RIGHT, $rangeRight->rangeType()); $this->assertSame('[2024-01-01, 2024-01-31)', $rangeRight->toISOString()); } @@ -96,7 +100,7 @@ public function test_contains_閉区間の境界値を含む(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act & Assert $this->assertTrue($range->contains($from)); // 開始境界 @@ -111,7 +115,7 @@ public function test_contains_開区間の境界値を含まない(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); - $range = LocalDateRange::from($from, $to, RangeType::OPEN); + $range = LocalDateRangeOpen::from($from, $to); // Act & Assert $this->assertFalse($range->contains($from)); // 開始境界 @@ -127,12 +131,12 @@ public function test_contains_半開区間の境界値(): void // Act & Assert // 左開区間 - $rangeLeft = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_LEFT); + $rangeLeft = LocalDateRangeHalfOpenLeft::from($from, $to); $this->assertFalse($rangeLeft->contains($from)); // 開始境界(含まない) $this->assertTrue($rangeLeft->contains($to)); // 終了境界(含む) // 右開区間 - $rangeRight = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_RIGHT); + $rangeRight = LocalDateRangeHalfOpenRight::from($from, $to); $this->assertTrue($rangeRight->contains($from)); // 開始境界(含む) $this->assertFalse($rangeRight->contains($to)); // 終了境界(含まない) } @@ -152,15 +156,25 @@ public function test_overlaps_comprehensive( string $description ): void { // Arrange - $range1 = LocalDateRange::from( + $range1Class = match ($range1Data['type']) { + RangeType::CLOSED => LocalDateRangeClosed::class, + RangeType::OPEN => LocalDateRangeOpen::class, + RangeType::HALF_OPEN_LEFT => LocalDateRangeHalfOpenLeft::class, + RangeType::HALF_OPEN_RIGHT => LocalDateRangeHalfOpenRight::class, + }; + $range1 = $range1Class::from( LocalDate::of($range1Data['from'][0], $range1Data['from'][1], $range1Data['from'][2]), LocalDate::of($range1Data['to'][0], $range1Data['to'][1], $range1Data['to'][2]), - $range1Data['type'] ); - $range2 = LocalDateRange::from( + $range2Class = match ($range2Data['type']) { + RangeType::CLOSED => LocalDateRangeClosed::class, + RangeType::OPEN => LocalDateRangeOpen::class, + RangeType::HALF_OPEN_LEFT => LocalDateRangeHalfOpenLeft::class, + RangeType::HALF_OPEN_RIGHT => LocalDateRangeHalfOpenRight::class, + }; + $range2 = $range2Class::from( LocalDate::of($range2Data['from'][0], $range2Data['from'][1], $range2Data['from'][2]), LocalDate::of($range2Data['to'][0], $range2Data['to'][1], $range2Data['to'][2]), - $range2Data['type'] ); // Act & Assert @@ -475,7 +489,7 @@ public function test_days_閉区間の日数計算(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 5); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $days = $range->days(); @@ -489,7 +503,7 @@ public function test_days_開区間の日数計算(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 5); - $range = LocalDateRange::from($from, $to, RangeType::OPEN); + $range = LocalDateRangeOpen::from($from, $to); // Act $days = $range->days(); @@ -505,8 +519,8 @@ public function test_days_半開区間の日数計算(): void $to = LocalDate::of(2024, 1, 5); // Act - $daysLeft = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_LEFT)->days(); - $daysRight = LocalDateRange::from($from, $to, RangeType::HALF_OPEN_RIGHT)->days(); + $daysLeft = LocalDateRangeHalfOpenLeft::from($from, $to)->days(); + $daysRight = LocalDateRangeHalfOpenRight::from($from, $to)->days(); // Assert $this->assertSame(4, $daysLeft); // 1日を含まず、5日を含む = 4日間 @@ -518,7 +532,7 @@ public function test_iterate_閉区間での日付の反復(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 3); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $dates = []; @@ -535,7 +549,7 @@ public function test_iterate_開区間での日付の反復(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 5); - $range = LocalDateRange::from($from, $to, RangeType::OPEN); + $range = LocalDateRangeOpen::from($from, $to); // Act $dates = []; @@ -552,8 +566,8 @@ public function test_equals_同じ範囲(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); - $range1 = LocalDateRange::from($from, $to, RangeType::CLOSED); - $range2 = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range1 = LocalDateRangeClosed::from($from, $to); + $range2 = LocalDateRangeClosed::from($from, $to); // Act & Assert $this->assertTrue($range1->equals($range2)); @@ -564,10 +578,11 @@ public function test_equals_異なる範囲(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); - $range1 = LocalDateRange::from($from, $to, RangeType::CLOSED); - $range2 = LocalDateRange::from($from, $to, RangeType::OPEN); + $range1 = LocalDateRangeClosed::from($from, $to); + $range2 = LocalDateRangeOpen::from($from, $to); // Act & Assert + // @phpstan-ignore-next-line $this->assertFalse($range1->equals($range2)); // 範囲タイプが異なる } @@ -576,7 +591,7 @@ public function test_jsonSerialize(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $json = $range->jsonSerialize(); @@ -595,7 +610,7 @@ public function test_from_デフォルトは右開区間(): void $range = LocalDateRange::from($from, $to); // Assert - $this->assertSame(RangeType::HALF_OPEN_RIGHT, $range->getRangeType()); + $this->assertSame(RangeType::HALF_OPEN_RIGHT, $range->rangeType()); $this->assertSame('[2024-01-01, 2024-01-31)', $range->toISOString()); } @@ -605,7 +620,7 @@ public function test_withFrom_新しい開始日付で範囲を作成(): void $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); $newFrom = LocalDate::of(2024, 1, 15); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $newRange = $range->withFrom($newFrom); @@ -613,7 +628,7 @@ public function test_withFrom_新しい開始日付で範囲を作成(): void // Assert $this->assertSame($newFrom, $newRange->getFrom()); $this->assertSame($to, $newRange->getTo()); - $this->assertSame(RangeType::CLOSED, $newRange->getRangeType()); + $this->assertSame(RangeType::CLOSED, $newRange->rangeType()); $this->assertSame('[2024-01-15, 2024-01-31]', $newRange->toISOString()); // 元の範囲は変更されていない $this->assertSame($from, $range->getFrom()); @@ -638,7 +653,7 @@ public function test_withTo_新しい終了日付で範囲を作成(): void $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); $newTo = LocalDate::of(2024, 1, 15); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $newRange = $range->withTo($newTo); @@ -646,7 +661,7 @@ public function test_withTo_新しい終了日付で範囲を作成(): void // Assert $this->assertSame($from, $newRange->getFrom()); $this->assertSame($newTo, $newRange->getTo()); - $this->assertSame(RangeType::CLOSED, $newRange->getRangeType()); + $this->assertSame(RangeType::CLOSED, $newRange->rangeType()); $this->assertSame('[2024-01-01, 2024-01-15]', $newRange->toISOString()); // 元の範囲は変更されていない $this->assertSame($to, $range->getTo()); @@ -671,7 +686,7 @@ public function test_tryWithFrom_有効な開始日付の場合成功(): void $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); $newFrom = LocalDate::of(2024, 1, 15); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $result = $range->tryWithFrom($newFrom); @@ -681,7 +696,7 @@ public function test_tryWithFrom_有効な開始日付の場合成功(): void $newRange = $result->unwrap(); $this->assertSame($newFrom, $newRange->getFrom()); $this->assertSame($to, $newRange->getTo()); - $this->assertSame(RangeType::CLOSED, $newRange->getRangeType()); + $this->assertSame(RangeType::CLOSED, $newRange->rangeType()); } public function test_tryWithFrom_無効な開始日付の場合エラー(): void @@ -709,7 +724,7 @@ public function test_tryWithTo_有効な終了日付の場合成功(): void $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 31); $newTo = LocalDate::of(2024, 1, 15); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $result = $range->tryWithTo($newTo); @@ -719,7 +734,7 @@ public function test_tryWithTo_有効な終了日付の場合成功(): void $newRange = $result->unwrap(); $this->assertSame($from, $newRange->getFrom()); $this->assertSame($newTo, $newRange->getTo()); - $this->assertSame(RangeType::CLOSED, $newRange->getRangeType()); + $this->assertSame(RangeType::CLOSED, $newRange->rangeType()); } public function test_tryWithTo_無効な終了日付の場合エラー(): void @@ -744,15 +759,13 @@ public function test_tryWithTo_無効な終了日付の場合エラー(): void public function test_strictlyBefore_完全に前にある範囲(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 10), - RangeType::CLOSED ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 20), LocalDate::of(2024, 1, 31), - RangeType::CLOSED ); // Act & Assert @@ -763,15 +776,13 @@ public function test_strictlyBefore_完全に前にある範囲(): void public function test_strictlyBefore_境界で接する範囲_閉区間(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::CLOSED ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::CLOSED ); // Act & Assert @@ -782,15 +793,13 @@ public function test_strictlyBefore_境界で接する範囲_閉区間(): void public function test_strictlyBefore_境界で接する範囲_開区間(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeOpen::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::OPEN ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeOpen::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::OPEN ); // Act & Assert @@ -801,15 +810,13 @@ public function test_strictlyBefore_境界で接する範囲_開区間(): void public function test_strictlyBefore_境界で接する範囲_右開区間と左開区間(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeHalfOpenRight::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::HALF_OPEN_RIGHT ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeHalfOpenRight::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::HALF_OPEN_LEFT ); // Act & Assert @@ -820,15 +827,13 @@ public function test_strictlyBefore_境界で接する範囲_右開区間と左 public function test_strictlyBefore_重なる範囲(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 20), - RangeType::CLOSED ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 10), LocalDate::of(2024, 1, 31), - RangeType::CLOSED ); // Act & Assert @@ -839,15 +844,13 @@ public function test_strictlyBefore_重なる範囲(): void public function test_strictlyBefore_境界で接する範囲_左開区間と右開区間の逆パターン(): void { // Arrange - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeHalfOpenLeft::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::HALF_OPEN_LEFT ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeHalfOpenRight::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::HALF_OPEN_RIGHT ); // Act & Assert @@ -859,15 +862,13 @@ public function test_strictlyBefore_閉区間と開区間の混在(): void { // Arrange // 閉区間の後に開区間 - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::CLOSED ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeOpen::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::OPEN ); // Act & Assert @@ -875,15 +876,13 @@ public function test_strictlyBefore_閉区間と開区間の混在(): void $this->assertFalse($range2->strictlyBefore($range1)); // 開区間の後に閉区間 - $range3 = LocalDateRange::from( + $range3 = LocalDateRangeOpen::from( LocalDate::of(2024, 2, 1), LocalDate::of(2024, 2, 15), - RangeType::OPEN ); - $range4 = LocalDateRange::from( + $range4 = LocalDateRangeClosed::from( LocalDate::of(2024, 2, 15), LocalDate::of(2024, 2, 28), - RangeType::CLOSED ); // Act & Assert @@ -895,15 +894,13 @@ public function test_strictlyBefore_閉区間と半開区間の混在(): void { // Arrange // 閉区間の後に左開区間 - $range1 = LocalDateRange::from( + $range1 = LocalDateRangeClosed::from( LocalDate::of(2024, 1, 1), LocalDate::of(2024, 1, 15), - RangeType::CLOSED ); - $range2 = LocalDateRange::from( + $range2 = LocalDateRangeHalfOpenLeft::from( LocalDate::of(2024, 1, 15), LocalDate::of(2024, 1, 31), - RangeType::HALF_OPEN_LEFT ); // Act & Assert @@ -911,15 +908,13 @@ public function test_strictlyBefore_閉区間と半開区間の混在(): void $this->assertFalse($range2->strictlyBefore($range1)); // 右開区間の後に閉区間 - $range3 = LocalDateRange::from( + $range3 = LocalDateRangeHalfOpenRight::from( LocalDate::of(2024, 2, 1), LocalDate::of(2024, 2, 15), - RangeType::HALF_OPEN_RIGHT ); - $range4 = LocalDateRange::from( + $range4 = LocalDateRangeClosed::from( LocalDate::of(2024, 2, 15), LocalDate::of(2024, 2, 28), - RangeType::CLOSED ); // Act & Assert @@ -932,7 +927,7 @@ public function test_count_閉区間の要素数(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 1, 5); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $count = $range->count(); @@ -945,7 +940,7 @@ public function test_count_同じ日付の範囲(): void { // Arrange $date = LocalDate::of(2024, 1, 1); - $range = LocalDateRange::from($date, $date, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($date, $date); // Act $count = $range->count(); @@ -959,7 +954,7 @@ public function test_count_年をまたぐ範囲(): void // Arrange $from = LocalDate::of(2023, 12, 30); $to = LocalDate::of(2024, 1, 2); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $count = $range->count(); @@ -973,7 +968,7 @@ public function test_count_大きな範囲(): void // Arrange $from = LocalDate::of(2024, 1, 1); $to = LocalDate::of(2024, 12, 31); - $range = LocalDateRange::from($from, $to, RangeType::CLOSED); + $range = LocalDateRangeClosed::from($from, $to); // Act $count = $range->count(); @@ -981,4 +976,67 @@ public function test_count_大きな範囲(): void // Assert $this->assertSame(366, $count); // 2024年は閏年で366日 } + + /** + * @dataProvider provideGetAsClosedCases + * @param LocalDateRange $range + */ + public function test_getAsClosed( + LocalDateRange $range, + LocalDate $expectedFrom, + LocalDate $expectedTo + ): void { + // Arrange + + // Act + $fromAsClosed = $range->getFromAsClosed(); + $toAsClosed = $range->getToAsClosed(); + + // Assert + $this->assertEquals($expectedFrom->toDateTimeImmutable(), $fromAsClosed->toDateTimeImmutable()); + $this->assertEquals($expectedTo->toDateTimeImmutable(), $toAsClosed->toDateTimeImmutable()); + } + + /** + * overlapsメソッドのテストデータプロバイダー + * + * @return array, expectedFrom: LocalDate, expectedTo: LocalDate}> + */ + public static function provideGetAsClosedCases(): iterable + { + return [ + '閉区間の場合' => [ + 'range' => LocalDateRangeClosed::from( + LocalDate::of(2024, 1, 1), + LocalDate::of(2024, 1, 31) + ), + 'expectedFrom' => LocalDate::of(2024, 1, 1), + 'expectedTo' => LocalDate::of(2024, 1, 31), + ], + '開区間の場合' => [ + 'range' => LocalDateRangeOpen::from( + LocalDate::of(2024, 1, 1), + LocalDate::of(2024, 1, 31) + ), + 'expectedFrom' => LocalDate::of(2024, 1, 2), + 'expectedTo' => LocalDate::of(2024, 1, 30), + ], + '左開区間の場合' => [ + 'range' => LocalDateRangeHalfOpenLeft::from( + LocalDate::of(2024, 1, 1), + LocalDate::of(2024, 1, 31) + ), + 'expectedFrom' => LocalDate::of(2024, 1, 2), + 'expectedTo' => LocalDate::of(2024, 1, 31), + ], + '右開区間の場合' => [ + 'range' => LocalDateRangeHalfOpenRight::from( + LocalDate::of(2024, 1, 1), + LocalDate::of(2024, 1, 31) + ), + 'expectedFrom' => LocalDate::of(2024, 1, 1), + 'expectedTo' => LocalDate::of(2024, 1, 30), + ], + ]; + } }