Skip to content

Commit 44f443c

Browse files
authored
Merge pull request #47 from wiz-develop:endou-mame/issue32
LocalDateRange の属性 from, to の型を継承時に指定できるようにする
2 parents 6fa904d + a09c561 commit 44f443c

File tree

7 files changed

+924
-313
lines changed

7 files changed

+924
-313
lines changed

.vscode/settings.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88
// php-cs-fixer settings
99
"php-cs-fixer.config": ".php-cs-fixer.dist.php",
1010
"php-cs-fixer.executablePath": "${workspaceFolder}/vendor/friendsofphp/php-cs-fixer/php-cs-fixer",
11-
// HACK: PHP8.4 に PHP-CS-Fixer が対応していないが強制的に実行するための設定
12-
// TODO: PHP-CS-Fixer が PHP8.4 に対応したら削除すること!!!!
13-
"php-cs-fixer.ignorePHPVersion": true,
1411
// PHPStan settings
12+
"phpstan.enabled": true,
1513
"phpstan.binPath": "vendor/bin/phpstan",
1614
"phpstan.configFile": "phpstan.neon.dist",
1715
"phpstan.singleFileMode": true,

examples/DateTime/TestLocalDateRange.php

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,30 @@
66

77
use WizDevelop\PhpValueObject\DateTime\LocalDate;
88
use WizDevelop\PhpValueObject\DateTime\LocalDateRange;
9+
use WizDevelop\PhpValueObject\DateTime\RangeType;
910

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

1213
// 1. 基本的な使用例:月間の範囲
1314
echo "=== 基本的な使用例 ===\n";
1415
$startOfMonth = LocalDate::of(2024, 1, 1);
1516
$endOfMonth = LocalDate::of(2024, 1, 31);
16-
$january = LocalDateRange::closed($startOfMonth, $endOfMonth);
17+
$january = LocalDateRange::from($startOfMonth, $endOfMonth);
1718

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

2122
// 2. 開区間と閉区間の違い
2223
echo "=== 開区間と閉区間の違い ===\n";
23-
$closedWeek = LocalDateRange::closed(
24+
$closedWeek = LocalDateRange::from(
2425
LocalDate::of(2024, 1, 1),
25-
LocalDate::of(2024, 1, 7)
26+
LocalDate::of(2024, 1, 7),
27+
RangeType::CLOSED,
2628
);
27-
$openWeek = LocalDateRange::open(
29+
$openWeek = LocalDateRange::from(
2830
LocalDate::of(2024, 1, 1),
29-
LocalDate::of(2024, 1, 7)
31+
LocalDate::of(2024, 1, 7),
32+
RangeType::OPEN,
3033
);
3134

3235
echo "閉区間(両端含む): {$closedWeek->toISOString()} = {$closedWeek->days()}\n";
@@ -35,9 +38,10 @@
3538
// 3. 半開区間の使用例(一般的な日付範囲の表現)
3639
echo "=== 半開区間の使用例 ===\n";
3740
// 月初から月末まで(月末を含まない一般的なパターン)
38-
$month = LocalDateRange::halfOpenRight(
41+
$month = LocalDateRange::from(
3942
LocalDate::of(2024, 1, 1),
40-
LocalDate::of(2024, 2, 1)
43+
LocalDate::of(2024, 2, 1),
44+
RangeType::HALF_OPEN_RIGHT,
4145
);
4246

4347
echo "1月(右半開区間): {$month->toISOString()}\n";
@@ -46,30 +50,34 @@
4650

4751
// 4. 日付の反復処理
4852
echo "=== 日付の反復処理 ===\n";
49-
$weekRange = LocalDateRange::closed(
53+
$weekRange = LocalDateRange::from(
5054
LocalDate::of(2024, 1, 1),
51-
LocalDate::of(2024, 1, 7)
55+
LocalDate::of(2024, 1, 7),
56+
RangeType::CLOSED,
5257
);
5358

5459
echo "1週間の日付:\n";
55-
foreach ($weekRange->iterate() as $date) {
60+
foreach ($weekRange->getIterator() as $date) {
5661
echo " - {$date->toISOString()}\n";
5762
}
5863
echo "\n";
5964

6065
// 5. 期間の重なり判定
6166
echo "=== 期間の重なり判定 ===\n";
62-
$q1 = LocalDateRange::closed(
67+
$q1 = LocalDateRange::from(
6368
LocalDate::of(2024, 1, 1),
64-
LocalDate::of(2024, 3, 31)
69+
LocalDate::of(2024, 3, 31),
70+
RangeType::CLOSED,
6571
);
66-
$q2 = LocalDateRange::closed(
72+
$q2 = LocalDateRange::from(
6773
LocalDate::of(2024, 4, 1),
68-
LocalDate::of(2024, 6, 30)
74+
LocalDate::of(2024, 6, 30),
75+
RangeType::CLOSED,
6976
);
70-
$marchToMay = LocalDateRange::closed(
77+
$marchToMay = LocalDateRange::from(
7178
LocalDate::of(2024, 3, 1),
72-
LocalDate::of(2024, 5, 31)
79+
LocalDate::of(2024, 5, 31),
80+
RangeType::CLOSED,
7381
);
7482

7583
echo "第1四半期: {$q1->toISOString()}\n";
@@ -81,9 +89,9 @@
8189

8290
// 6. 特定の日付が期間内かチェック
8391
echo "=== 期間内チェック ===\n";
84-
$vacation = LocalDateRange::closed(
92+
$vacation = LocalDateRange::from(
8593
LocalDate::of(2024, 8, 10),
86-
LocalDate::of(2024, 8, 20)
94+
LocalDate::of(2024, 8, 20),
8795
);
8896
$checkDate = LocalDate::of(2024, 8, 15);
8997

@@ -94,7 +102,8 @@
94102
echo "=== エラーハンドリング ===\n";
95103
$invalidResult = LocalDateRange::tryFrom(
96104
LocalDate::of(2024, 12, 31),
97-
LocalDate::of(2024, 1, 1)
105+
LocalDate::of(2024, 1, 1),
106+
RangeType::CLOSED,
98107
);
99108

100109
if ($invalidResult->isErr()) {
@@ -108,16 +117,12 @@
108117
$startDate = LocalDate::of(2024, 1, 1);
109118
$endDate = null;
110119

111-
$optionRange = LocalDateRange::fromNullable($startDate, $endDate);
112-
if ($optionRange->isNone()) {
113-
echo "範囲を作成できませんでした(いずれかの値がnullです)\n";
114-
}
115-
116120
// 9. 年間カレンダーの例
117121
echo "\n=== 年間カレンダーの例 ===\n";
118-
$year2024 = LocalDateRange::closed(
122+
$year2024 = LocalDateRange::from(
119123
LocalDate::of(2024, 1, 1),
120-
LocalDate::of(2024, 12, 31)
124+
LocalDate::of(2024, 12, 31),
125+
RangeType::CLOSED,
121126
);
122127

123128
echo "2024年: {$year2024->toISOString()}\n";

src/DateTime/LocalDate.php

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
use WizDevelop\PhpValueObject\ValueObjectMeta;
1717

1818
/**
19+
* @phpstan-type Year int<LocalDate::MIN_YEAR, LocalDate::MAX_YEAR>
20+
* @phpstan-type Month int<1, 12>
21+
* @phpstan-type Day int<1, 31>
22+
*
1923
* ローカル日付を表す値オブジェクト
2024
*/
2125
#[ValueObjectMeta(name: 'ローカル日付')]
@@ -43,9 +47,9 @@
4347

4448
/**
4549
* Avoid new() operator.
46-
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
47-
* @param int<1, 12> $month the month, from 1 to 12
48-
* @param int<1, 31> $day the day, from 1 to 31
50+
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
51+
* @param Month $month the month, from 1 to 12
52+
* @param Day $day the day, from 1 to 31
4953
*/
5054
final private function __construct(
5155
private int $year,
@@ -85,9 +89,9 @@ final public function jsonSerialize(): string
8589
// MARK: factory methods
8690
// -------------------------------------------------------------------------
8791
/**
88-
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
89-
* @param int<1, 12> $month the month, from 1 to 12
90-
* @param int<1, 31> $day the day, from 1 to 31
92+
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
93+
* @param Month $month the month, from 1 to 12
94+
* @param Day $day the day, from 1 to 31
9195
*/
9296
final public static function of(int $year, int $month, int $day): static
9397
{
@@ -146,7 +150,7 @@ final public static function tryFromNullable(?DateTimeInterface $value): Result
146150
return static::tryFrom($value)->map(static fn ($result) => Option\some($result));
147151
}
148152

149-
final public static function now(DateTimeZone $timeZone): static
153+
final public static function now(DateTimeZone $timeZone = new DateTimeZone('Asia/Tokyo')): static
150154
{
151155
$value = new DateTimeImmutable('now', $timeZone);
152156

@@ -155,6 +159,11 @@ final public static function now(DateTimeZone $timeZone): static
155159
return static::of($year, $month, $day);
156160
}
157161

162+
final public static function max(): static
163+
{
164+
return static::of(self::MAX_YEAR, 12, 31);
165+
}
166+
158167
/**
159168
* Obtains an instance of `LocalDate` from the epoch day count.
160169
*
@@ -186,10 +195,10 @@ final public static function ofEpochDay(int $epochDay): static
186195
// Convert march-based values back to January-based.
187196
$marchMonth0 = intdiv($marchDoy0 * 5 + 2, 153);
188197

189-
/** @var int<1, 12> $month */
198+
/** @var Month $month */
190199
$month = ($marchMonth0 + 2) % 12 + 1;
191200

192-
/** @var int<1, 31> $dom */
201+
/** @var Day $dom */
193202
$dom = $marchDoy0 - intdiv($marchMonth0 * 306 + 5, 10) + 1;
194203

195204
$yearEst += intdiv($marchMonth0, 10);
@@ -260,16 +269,14 @@ className: static::class,
260269
);
261270
}
262271

263-
264-
265272
return Result\ok(true);
266273
}
267274

268275
/**
269276
* 有効な日かどうかを判定
270277
* @param int $year 年
271-
* @param int<1, 12> $monthOfYear 月
272-
* @param int<1, 31> $dayOfMonth 日
278+
* @param Month $monthOfYear 月
279+
* @param Day $dayOfMonth 日
273280
* @return Result<bool,ValueObjectError>
274281
*/
275282
final protected static function isValidDate(int $year, int $monthOfYear, int $dayOfMonth): Result
@@ -337,7 +344,7 @@ final public function toISOString(): string
337344
}
338345

339346
/**
340-
* @return int<self::MIN_YEAR, self::MAX_YEAR>
347+
* @return Year
341348
*/
342349
final public function getYear(): int
343350
{
@@ -346,15 +353,15 @@ final public function getYear(): int
346353
}
347354

348355
/**
349-
* @return int<1, 12>
356+
* @return Month
350357
*/
351358
final public function getMonth(): int
352359
{
353360
return $this->month;
354361
}
355362

356363
/**
357-
* @return int<1, 31>
364+
* @return Day
358365
*/
359366
final public function getDay(): int
360367
{
@@ -409,6 +416,11 @@ final public function compareTo(self $that): int
409416
return 0;
410417
}
411418

419+
final public function is(self $that): bool
420+
{
421+
return $this->compareTo($that) === 0;
422+
}
423+
412424
final public function isBefore(self $that): bool
413425
{
414426
return $this->compareTo($that) === -1;
@@ -463,7 +475,7 @@ final public function addMonths(int $months): static
463475

464476
$yearDiff = Math::floorDiv($month, 12);
465477

466-
/** @var int<1, 12> $month */
478+
/** @var Month $month */
467479
$month = Math::floorMod($month, 12) + 1;
468480

469481
$year = $this->year + $yearDiff;
@@ -591,17 +603,17 @@ final public function toEpochDay(): int
591603
// MARK: private methods
592604
// -------------------------------------------------------------------------
593605
/**
594-
* @return array{0:int<self::MIN_YEAR, self::MAX_YEAR>, 1:int<1, 12>, 2:int<1, 31>}
606+
* @return array{0:Year, 1:Month, 2:Day}
595607
*/
596608
private static function extractDate(DateTimeInterface $value): array
597609
{
598-
/** @var int<self::MIN_YEAR, self::MAX_YEAR> */
610+
/** @var Year */
599611
$year = (int)$value->format('Y');
600612

601-
/** @var int<1, 12> */
613+
/** @var Month */
602614
$month = (int)$value->format('n');
603615

604-
/** @var int<1, 31> */
616+
/** @var Day */
605617
$day = (int)$value->format('j');
606618

607619
return [$year, $month, $day];
@@ -610,9 +622,9 @@ private static function extractDate(DateTimeInterface $value): array
610622
/**
611623
* Resolves the date, resolving days past the end of month.
612624
*
613-
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
614-
* @param int<1, 12> $month the month-of-year to represent
615-
* @param int<1, 31> $day the day-of-month to represent, validated from 1 to 31
625+
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
626+
* @param Month $month the month-of-year to represent
627+
* @param Day $day the day-of-month to represent, validated from 1 to 31
616628
*/
617629
private static function resolvePreviousValid(int $year, int $month, int $day): static
618630
{
@@ -632,7 +644,7 @@ private static function isLeapYear(int $year): bool
632644
}
633645

634646
/**
635-
* @param int<1, 12> $month
647+
* @param Month $month
636648
* @return int<28, 31>
637649
*/
638650
private static function lengthOfMonth(int $year, int $month): int
@@ -646,8 +658,8 @@ private static function lengthOfMonth(int $year, int $month): int
646658

647659
/**
648660
* Returns whether this date is the last day of the month.
649-
* @param int<1, 12> $month
650-
* @param int<1, 31> $day
661+
* @param Month $month
662+
* @param Day $day
651663
*/
652664
private static function isEndOfMonth(int $year, int $month, int $day): bool
653665
{

0 commit comments

Comments
 (0)