Skip to content

feat: LocalDateRangeとLocalDateTimeRangeクラスを実装 #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 11, 2025
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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "wiz-develop/php-value-object",
"description": "📦 The PHP Value Object library offers immutable, type-safe, and self-validating objects to model domain values using the Value Object pattern.",
"require": {
"php": ">=8.4"
"php": ">=8.4",
"wiz-develop/php-monad": "^2.2"
},
"type": "library",
"license": "MIT",
Expand All @@ -28,7 +29,6 @@
"wiz-develop/php-cs-fixer-config": "^8.3",
"phpunit/php-code-coverage": "^11.0",
"phpunit/phpunit": "^11.3",
"wiz-develop/php-monad": "^2.2",
"phpstan/phpstan": "^2.1",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-strict-rules": "^2.0"
Expand Down
296 changes: 155 additions & 141 deletions composer.lock

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions examples/DateTime/TestLocalDateRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\Examples\DateTime;

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

require_once __DIR__ . '/../../vendor/autoload.php';

// 1. 基本的な使用例:月間の範囲
echo "=== 基本的な使用例 ===\n";
$startOfMonth = LocalDate::of(2024, 1, 1);
$endOfMonth = LocalDate::of(2024, 1, 31);
$january = LocalDateRange::closed($startOfMonth, $endOfMonth);

echo "1月の期間: {$january->toISOString()}\n";
echo "日数: {$january->days()} 日\n\n";

// 2. 開区間と閉区間の違い
echo "=== 開区間と閉区間の違い ===\n";
$closedWeek = LocalDateRange::closed(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 1, 7)
);
$openWeek = LocalDateRange::open(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 1, 7)
);

echo "閉区間(両端含む): {$closedWeek->toISOString()} = {$closedWeek->days()} 日\n";
echo "開区間(両端含まない): {$openWeek->toISOString()} = {$openWeek->days()} 日\n\n";

// 3. 半開区間の使用例(一般的な日付範囲の表現)
echo "=== 半開区間の使用例 ===\n";
// 月初から月末まで(月末を含まない一般的なパターン)
$month = LocalDateRange::halfOpenRight(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 2, 1)
);

echo "1月(右半開区間): {$month->toISOString()}\n";
echo '1月31日を含む: ' . ($month->contains(LocalDate::of(2024, 1, 31)) ? 'はい' : 'いいえ') . "\n";
echo '2月1日を含む: ' . ($month->contains(LocalDate::of(2024, 2, 1)) ? 'はい' : 'いいえ') . "\n\n";

// 4. 日付の反復処理
echo "=== 日付の反復処理 ===\n";
$weekRange = LocalDateRange::closed(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 1, 7)
);

echo "1週間の日付:\n";
foreach ($weekRange->iterate() as $date) {
echo " - {$date->toISOString()}\n";
}
echo "\n";

// 5. 期間の重なり判定
echo "=== 期間の重なり判定 ===\n";
$q1 = LocalDateRange::closed(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 3, 31)
);
$q2 = LocalDateRange::closed(
LocalDate::of(2024, 4, 1),
LocalDate::of(2024, 6, 30)
);
$marchToMay = LocalDateRange::closed(
LocalDate::of(2024, 3, 1),
LocalDate::of(2024, 5, 31)
);

echo "第1四半期: {$q1->toISOString()}\n";
echo "第2四半期: {$q2->toISOString()}\n";
echo "3月〜5月: {$marchToMay->toISOString()}\n";
echo '第1四半期と第2四半期が重なる: ' . ($q1->overlaps($q2) ? 'はい' : 'いいえ') . "\n";
echo '第1四半期と3月〜5月が重なる: ' . ($q1->overlaps($marchToMay) ? 'はい' : 'いいえ') . "\n";
echo '第2四半期と3月〜5月が重なる: ' . ($q2->overlaps($marchToMay) ? 'はい' : 'いいえ') . "\n\n";

// 6. 特定の日付が期間内かチェック
echo "=== 期間内チェック ===\n";
$vacation = LocalDateRange::closed(
LocalDate::of(2024, 8, 10),
LocalDate::of(2024, 8, 20)
);
$checkDate = LocalDate::of(2024, 8, 15);

echo "休暇期間: {$vacation->toISOString()}\n";
echo "{$checkDate->toISOString()} は休暇中: " . ($vacation->contains($checkDate) ? 'はい' : 'いいえ') . "\n\n";

// 7. エラーハンドリング
echo "=== エラーハンドリング ===\n";
$invalidResult = LocalDateRange::tryFrom(
LocalDate::of(2024, 12, 31),
LocalDate::of(2024, 1, 1)
);

if ($invalidResult->isErr()) {
$error = $invalidResult->unwrapErr();
echo "エラー: {$error->getMessage()}\n";
echo "エラーコード: {$error->getCode()}\n";
}

// 8. nullable対応
echo "\n=== Nullable対応 ===\n";
$startDate = LocalDate::of(2024, 1, 1);
$endDate = null;

$optionRange = LocalDateRange::fromNullable($startDate, $endDate);
if ($optionRange->isNone()) {
echo "範囲を作成できませんでした(いずれかの値がnullです)\n";
}

// 9. 年間カレンダーの例
echo "\n=== 年間カレンダーの例 ===\n";
$year2024 = LocalDateRange::closed(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 12, 31)
);

echo "2024年: {$year2024->toISOString()}\n";
echo "日数: {$year2024->days()} 日(うるう年)\n";
97 changes: 97 additions & 0 deletions examples/DateTime/TestLocalDateTimeRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\Examples\DateTime;

use DateTimeImmutable;
use DateTimeZone;
use WizDevelop\PhpValueObject\DateTime\LocalDateTime;
use WizDevelop\PhpValueObject\DateTime\LocalDateTimeRange;

require_once __DIR__ . '/../../vendor/autoload.php';

// 1. 基本的な使用例:閉区間での範囲作成
echo "=== 基本的な使用例 ===\n";
$start = LocalDateTime::from(new DateTimeImmutable('2024-01-01 09:00:00'));
$end = LocalDateTime::from(new DateTimeImmutable('2024-01-01 18:00:00'));
$workingHours = LocalDateTimeRange::closed($start, $end);

echo "営業時間: {$workingHours->toISOString()}\n";
echo "期間(時間): {$workingHours->durationInHours()} 時間\n\n";

// 2. 開区間と閉区間の違い
echo "=== 開区間と閉区間の違い ===\n";
$closedRange = LocalDateTimeRange::closed($start, $end);
$openRange = LocalDateTimeRange::open($start, $end);

$testTime = $start; // 開始時刻でテスト
echo "テスト時刻: {$testTime->toISOString()}\n";
echo '閉区間に含まれる: ' . ($closedRange->contains($testTime) ? 'はい' : 'いいえ') . "\n";
echo '開区間に含まれる: ' . ($openRange->contains($testTime) ? 'はい' : 'いいえ') . "\n\n";

// 3. 半開区間の使用例
echo "=== 半開区間の使用例 ===\n";
// イベントのスケジュール(開始時刻を含み、終了時刻を含まない)
$event1 = LocalDateTimeRange::halfOpenRight(
LocalDateTime::from(new DateTimeImmutable('2024-01-01 10:00:00')),
LocalDateTime::from(new DateTimeImmutable('2024-01-01 12:00:00'))
);
$event2 = LocalDateTimeRange::halfOpenRight(
LocalDateTime::from(new DateTimeImmutable('2024-01-01 12:00:00')),
LocalDateTime::from(new DateTimeImmutable('2024-01-01 14:00:00'))
);

echo "イベント1: {$event1->toISOString()}\n";
echo "イベント2: {$event2->toISOString()}\n";
echo 'イベントが重なる: ' . ($event1->overlaps($event2) ? 'はい' : 'いいえ') . "\n\n";

// 4. 現在時刻が営業時間内かチェック
echo "=== 営業時間チェック ===\n";
$now = LocalDateTime::now(new DateTimeZone('Asia/Tokyo'));
$businessHours = LocalDateTimeRange::closed(
LocalDateTime::from(new DateTimeImmutable('today 09:00:00')),
LocalDateTime::from(new DateTimeImmutable('today 18:00:00'))
);

echo "現在時刻: {$now->toISOString()}\n";
echo "営業時間: {$businessHours->toISOString()}\n";
echo '営業中: ' . ($businessHours->contains($now) ? 'はい' : 'いいえ') . "\n\n";

// 5. 期間の重なり判定
echo "=== 期間の重なり判定 ===\n";
$meeting1 = LocalDateTimeRange::closed(
LocalDateTime::from(new DateTimeImmutable('2024-01-01 14:00:00')),
LocalDateTime::from(new DateTimeImmutable('2024-01-01 15:30:00'))
);
$meeting2 = LocalDateTimeRange::closed(
LocalDateTime::from(new DateTimeImmutable('2024-01-01 15:00:00')),
LocalDateTime::from(new DateTimeImmutable('2024-01-01 16:00:00'))
);

echo "会議1: {$meeting1->toISOString()}\n";
echo "会議2: {$meeting2->toISOString()}\n";
echo 'スケジュールが衝突: ' . ($meeting1->overlaps($meeting2) ? 'はい' : 'いいえ') . "\n\n";

// 6. エラーハンドリング
echo "=== エラーハンドリング ===\n";
$invalidResult = LocalDateTimeRange::tryFrom(
LocalDateTime::from(new DateTimeImmutable('2024-01-01 18:00:00')),
LocalDateTime::from(new DateTimeImmutable('2024-01-01 09:00:00'))
);

if ($invalidResult->isErr()) {
$error = $invalidResult->unwrapErr();
echo "エラー: {$error->getMessage()}\n";
echo "エラーコード: {$error->getCode()}\n";
}

// 7. nullable対応
echo "\n=== Nullable対応 ===\n";
$fromTime = LocalDateTime::from(new DateTimeImmutable('2024-01-01 09:00:00'));
$toTime = null;

$optionRange = LocalDateTimeRange::fromNullable($fromTime, $toTime);
if ($optionRange->isNone()) {
echo "範囲を作成できませんでした(いずれかの値がnullです)\n";
}
Loading
Loading