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
4 changes: 1 addition & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
// php-cs-fixer settings
"php-cs-fixer.config": ".php-cs-fixer.dist.php",
"php-cs-fixer.executablePath": "${workspaceFolder}/vendor/friendsofphp/php-cs-fixer/php-cs-fixer",
// HACK: PHP8.4 に PHP-CS-Fixer が対応していないが強制的に実行するための設定
// TODO: PHP-CS-Fixer が PHP8.4 に対応したら削除すること!!!!
"php-cs-fixer.ignorePHPVersion": true,
// PHPStan settings
"phpstan.enabled": true,
"phpstan.binPath": "vendor/bin/phpstan",
"phpstan.configFile": "phpstan.neon.dist",
"phpstan.singleFileMode": true,
Expand Down
57 changes: 31 additions & 26 deletions examples/DateTime/TestLocalDateRange.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@

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

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);
$january = LocalDateRange::from($startOfMonth, $endOfMonth);

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

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

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

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

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

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

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

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

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

Expand All @@ -94,7 +102,8 @@
echo "=== エラーハンドリング ===\n";
$invalidResult = LocalDateRange::tryFrom(
LocalDate::of(2024, 12, 31),
LocalDate::of(2024, 1, 1)
LocalDate::of(2024, 1, 1),
RangeType::CLOSED,
);

if ($invalidResult->isErr()) {
Expand All @@ -108,16 +117,12 @@
$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(
$year2024 = LocalDateRange::from(
LocalDate::of(2024, 1, 1),
LocalDate::of(2024, 12, 31)
LocalDate::of(2024, 12, 31),
RangeType::CLOSED,
);

echo "2024年: {$year2024->toISOString()}\n";
Expand Down
66 changes: 39 additions & 27 deletions src/DateTime/LocalDate.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
use WizDevelop\PhpValueObject\ValueObjectMeta;

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

/**
* Avoid new() operator.
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param int<1, 12> $month the month, from 1 to 12
* @param int<1, 31> $day the day, from 1 to 31
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param Month $month the month, from 1 to 12
* @param Day $day the day, from 1 to 31
*/
final private function __construct(
private int $year,
Expand Down Expand Up @@ -85,9 +89,9 @@ final public function jsonSerialize(): string
// MARK: factory methods
// -------------------------------------------------------------------------
/**
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param int<1, 12> $month the month, from 1 to 12
* @param int<1, 31> $day the day, from 1 to 31
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param Month $month the month, from 1 to 12
* @param Day $day the day, from 1 to 31
*/
final public static function of(int $year, int $month, int $day): static
{
Expand Down Expand Up @@ -146,7 +150,7 @@ final public static function tryFromNullable(?DateTimeInterface $value): Result
return static::tryFrom($value)->map(static fn ($result) => Option\some($result));
}

final public static function now(DateTimeZone $timeZone): static
final public static function now(DateTimeZone $timeZone = new DateTimeZone('Asia/Tokyo')): static
{
$value = new DateTimeImmutable('now', $timeZone);

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

final public static function max(): static
{
return static::of(self::MAX_YEAR, 12, 31);
}

/**
* Obtains an instance of `LocalDate` from the epoch day count.
*
Expand Down Expand Up @@ -186,10 +195,10 @@ final public static function ofEpochDay(int $epochDay): static
// Convert march-based values back to January-based.
$marchMonth0 = intdiv($marchDoy0 * 5 + 2, 153);

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

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

$yearEst += intdiv($marchMonth0, 10);
Expand Down Expand Up @@ -260,16 +269,14 @@ className: static::class,
);
}



return Result\ok(true);
}

/**
* 有効な日かどうかを判定
* @param int $year 年
* @param int<1, 12> $monthOfYear 月
* @param int<1, 31> $dayOfMonth 日
* @param Month $monthOfYear 月
* @param Day $dayOfMonth 日
* @return Result<bool,ValueObjectError>
*/
final protected static function isValidDate(int $year, int $monthOfYear, int $dayOfMonth): Result
Expand Down Expand Up @@ -337,7 +344,7 @@ final public function toISOString(): string
}

/**
* @return int<self::MIN_YEAR, self::MAX_YEAR>
* @return Year
*/
final public function getYear(): int
{
Expand All @@ -346,15 +353,15 @@ final public function getYear(): int
}

/**
* @return int<1, 12>
* @return Month
*/
final public function getMonth(): int
{
return $this->month;
}

/**
* @return int<1, 31>
* @return Day
*/
final public function getDay(): int
{
Expand Down Expand Up @@ -409,6 +416,11 @@ final public function compareTo(self $that): int
return 0;
}

final public function is(self $that): bool
{
return $this->compareTo($that) === 0;
}

final public function isBefore(self $that): bool
{
return $this->compareTo($that) === -1;
Expand Down Expand Up @@ -463,7 +475,7 @@ final public function addMonths(int $months): static

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

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

$year = $this->year + $yearDiff;
Expand Down Expand Up @@ -591,17 +603,17 @@ final public function toEpochDay(): int
// MARK: private methods
// -------------------------------------------------------------------------
/**
* @return array{0:int<self::MIN_YEAR, self::MAX_YEAR>, 1:int<1, 12>, 2:int<1, 31>}
* @return array{0:Year, 1:Month, 2:Day}
*/
private static function extractDate(DateTimeInterface $value): array
{
/** @var int<self::MIN_YEAR, self::MAX_YEAR> */
/** @var Year */
$year = (int)$value->format('Y');

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

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

return [$year, $month, $day];
Expand All @@ -610,9 +622,9 @@ private static function extractDate(DateTimeInterface $value): array
/**
* Resolves the date, resolving days past the end of month.
*
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param int<1, 12> $month the month-of-year to represent
* @param int<1, 31> $day the day-of-month to represent, validated from 1 to 31
* @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR
* @param Month $month the month-of-year to represent
* @param Day $day the day-of-month to represent, validated from 1 to 31
*/
private static function resolvePreviousValid(int $year, int $month, int $day): static
{
Expand All @@ -632,7 +644,7 @@ private static function isLeapYear(int $year): bool
}

/**
* @param int<1, 12> $month
* @param Month $month
* @return int<28, 31>
*/
private static function lengthOfMonth(int $year, int $month): int
Expand All @@ -646,8 +658,8 @@ private static function lengthOfMonth(int $year, int $month): int

/**
* Returns whether this date is the last day of the month.
* @param int<1, 12> $month
* @param int<1, 31> $day
* @param Month $month
* @param Day $day
*/
private static function isEndOfMonth(int $year, int $month, int $day): bool
{
Expand Down
Loading
Loading