24時超えの終業時刻ってw
担当した勤怠管理プロジェクトにて、24時超えの終業時刻(どころか始業すら)が当たり前のようにあった。
フォームからの入力値を正しい日時オブジェクトに変換するためにも Carbon::parse を用いたかったが、残念ながらエラーで取得できない。
24時超えや31日超えの不正な日時でも取得可能なのは、Carbon::create だけのようだ。
ということで、Carbon::parse を拡張してみた。
Carbon::parse の拡張
前の記事で App\Helpers\Carbon
を作成しているので、そこにメソッドを加える。
app/Helpers/Carbon.php
<?php
namespace App\Helpers;
use Carbon\Carbon as BaseCarbon;
use Carbon\CarbonImmutable as BaseCarbonImmutable;
class Carbon extends BaseCarbonImmutable
{
/**
* {@inheritdoc}
*/
public static function setTestNow($testNow = null)
{
BaseCarbon::setTestNow($testNow);
BaseCarbonImmutable::setTestNow($testNow);
}
/**
* Create a carbon instance from a string.
*
* @param string|DateTimeInterface|null $time
* @return static
*/
public static function parse($time = null, $tz = null)
{
try {
// '-1 month' などが扱えるよう、まずは parse してみる
return parent::parse($time, $tz);
} catch (\Exception $e) {
// 24時超えや31日超えでエラーとなった場合
if (preg_match('/^(\\d+?)[\\/-](\\d+?)[\\/-](\\d+) (\\d+):(\\d+):?(\\d*)$/', $time, $m)) {
// 2021-10-10 10:10:00 または 2021/10/10 10:10:00
return parent::create((int) $m[1], (int) $m[2], (int) $m[3], (int) $m[4], (int) $m[5], $m[6] ?? 0, $tz);
} elseif (preg_match('/^(\\d+?)[\\/-](\\d+?)[\\/-](\\d+)$/', $time, $m)) {
// 2021-10-10 または 2021/10/10
return parent::create((int) $m[1], (int) $m[2], (int) $m[3], 0, 0, 0, $tz);
} elseif (preg_match('/^(\\d+):(\\d+):?(\\d*)$/', $time, $m)) {
// 10:10:00
$now = now();
return parent::create($now->year, $now->month, $now->day, (int) $m[1], (int) $m[2], (int) $m[3] ?? 0, $tz);
}
}
return null;
}
}
動作確認
比較のため strtotime の結果も記す。
オリジナルカーボンである Carbon\Carbon と、今回置き換えた App\Helpers\Carbon との結果の違いを見てほしい。
「2月30日」はすべて3月に繰り越せる。
>>> date('Y-m-d', strtotime('2022-02-30'))
=> "2022-03-02"
>>> Carbon\Carbon::parse('2022-02-30')
=> Carbon\Carbon @1646146800 {#4429
date: 2022-03-02 00:00:00.0 Asia/Tokyo (+09:00),
}
>>> Carbon::parse('2022-02-30')
=> App\Helpers\Carbon @1646146800 {#4416
date: 2022-03-02 00:00:00.0 Asia/Tokyo (+09:00),
}
>>>
31日超えがエラーにならないのは App\Helpers\Carbon だけ。
>>> date('Y-m-d', strtotime('2022-02-32'))
=> "1970-01-01"
>>> Carbon\Carbon::parse('2022-02-32')
Carbon\Exceptions\InvalidFormatException with message 'Could not parse '2022-02-32': DateTime::__construct(): Failed to parse time string (2022-02-32) at position 9 (2): Unexpected character'
>>> Carbon::parse('2022-02-32')
=> App\Helpers\Carbon @1646319600 {#4323
date: 2022-03-04 00:00:00.0 Asia/Tokyo (+09:00),
}
時刻として「10:10」はすべて読み取れる。日付部分は「今日」が基準となる。
>>> date('Y-m-d H:i:s', strtotime('10:10'))
=> "2022-05-19 10:10:00"
>>> Carbon\Carbon::parse('10:10')
=> Carbon\Carbon @1652922600 {#4440
date: 2022-05-19 10:10:00.0 Asia/Tokyo (+09:00),
}
>>> Carbon::parse('10:10')
=> App\Helpers\Carbon @1652922600 {#4323
date: 2022-05-19 10:10:00.0 Asia/Tokyo (+09:00),
}
24時超えの時刻が読み取れるのは App\Helpers\Carbon だけ。
>>> date('Y-m-d H:i:s', strtotime('25:10'))
=> "1970-01-01 09:00:00"
>>> Carbon\Carbon::parse('25:10')
Carbon\Exceptions\InvalidFormatException with message 'Could not parse '25:10': DateTime::__construct(): Failed to parse time string (25:10) at position 0 (2): Unexpected character'
>>> Carbon::parse('25:10')
=> App\Helpers\Carbon @1652976600 {#4290
date: 2022-05-20 01:10:00.0 Asia/Tokyo (+09:00),
}
Eloquent の日時はすでに Carbon だが、再パースしてもOK。
>>> $date = User::find(1)->created_at
=> App\Helpers\Carbon @1652765323 {#4319
date: 2022-05-17 14:28:43.0 Asia/Tokyo (+09:00),
}
>>> Carbon::parse($date)
=> App\Helpers\Carbon @1652765323 {#4407
date: 2022-05-17 14:28:43.0 Asia/Tokyo (+09:00),
}