Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions examples/DateTime/TestLocalDateRange.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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";
Expand All @@ -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()) {
Expand All @@ -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";
Expand Down
68 changes: 45 additions & 23 deletions src/DateTime/LocalDateRange.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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());
Expand All @@ -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]
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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)));
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -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 => ')',
};
Expand All @@ -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),
};
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -197,20 +219,20 @@ 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);
}

/**
* 指定された日付が範囲内に含まれるかを判定
*/
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),
};
Expand Down Expand Up @@ -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
)
);
}
Expand All @@ -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, // 片方の端を含む
Expand All @@ -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),
};
Expand Down
22 changes: 22 additions & 0 deletions src/DateTime/LocalDateRange/LocalDateRangeClosed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\DateTime\LocalDateRange;

use Override;
use WizDevelop\PhpValueObject\DateTime\LocalDate;
use WizDevelop\PhpValueObject\DateTime\LocalDateRange;
use WizDevelop\PhpValueObject\DateTime\RangeType;

/**
* @extends LocalDateRange<LocalDate, LocalDate>
*/
final readonly class LocalDateRangeClosed extends LocalDateRange
{
#[Override]
public static function rangeType(): RangeType
{
return RangeType::CLOSED;
}
}
22 changes: 22 additions & 0 deletions src/DateTime/LocalDateRange/LocalDateRangeHalfOpenLeft.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\DateTime\LocalDateRange;

use Override;
use WizDevelop\PhpValueObject\DateTime\LocalDate;
use WizDevelop\PhpValueObject\DateTime\LocalDateRange;
use WizDevelop\PhpValueObject\DateTime\RangeType;

/**
* @extends LocalDateRange<LocalDate, LocalDate>
*/
final readonly class LocalDateRangeHalfOpenLeft extends LocalDateRange
{
#[Override]
public static function rangeType(): RangeType
{
return RangeType::HALF_OPEN_LEFT;
}
}
22 changes: 22 additions & 0 deletions src/DateTime/LocalDateRange/LocalDateRangeHalfOpenRight.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\DateTime\LocalDateRange;

use Override;
use WizDevelop\PhpValueObject\DateTime\LocalDate;
use WizDevelop\PhpValueObject\DateTime\LocalDateRange;
use WizDevelop\PhpValueObject\DateTime\RangeType;

/**
* @extends LocalDateRange<LocalDate, LocalDate>
*/
final readonly class LocalDateRangeHalfOpenRight extends LocalDateRange
{
#[Override]
public static function rangeType(): RangeType
{
return RangeType::HALF_OPEN_RIGHT;
}
}
22 changes: 22 additions & 0 deletions src/DateTime/LocalDateRange/LocalDateRangeOpen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\DateTime\LocalDateRange;

use Override;
use WizDevelop\PhpValueObject\DateTime\LocalDate;
use WizDevelop\PhpValueObject\DateTime\LocalDateRange;
use WizDevelop\PhpValueObject\DateTime\RangeType;

/**
* @extends LocalDateRange<LocalDate, LocalDate>
*/
final readonly class LocalDateRangeOpen extends LocalDateRange
{
#[Override]
public static function rangeType(): RangeType
{
return RangeType::OPEN;
}
}
Loading
Loading