🍣
Laravelでタイムゾーン付きISO8601のdatetime値がおかしい
前提条件
config/app.php
の timezone
が Asia/Tokyo
になっている
期待動作
ISO8601フォーマットをタイムゾーン情報持ちながらパースしてくれる
>>> (new Carbon\Carbon('2022-10-18T15:00:00.000000Z'))
=> Carbon\Carbon @1666105200 {#0000
date: 2022-10-18 15:00:00.0 +00:00,
}
>>> (new Carbon\Carbon('2022-10-18T15:00:00.000000Z'))->tz
=> Carbon\CarbonTimeZone {#0000
timezone: +00:00,
}
Modelでの挙動
ISO8601のタイムゾーンが無視される
>>> $class = new class() extends \Illuminate\Database\Eloquent\Model { protected $guarded = []; protected $casts = ['expired_at' => 'datetime']; }
=> Illuminate\Database\Eloquent\Model@anonymous {#0000}
>>> (new $class(['expired_at' => '2022-10-18T15:00:00.000000Z']))->expired_at
=> Illuminate\Support\Carbon @1666072800 {#0000
date: 2022-10-18 15:00:00.0 Asia/Tokyo (+09:00),
}
>>> (new $class(['expired_at' => '2022-10-18T15:00:00.000000Z']))->expired_at->tz
=> Carbon\CarbonTimeZone {#0000
timezone: Asia/Tokyo (+09:00),
}
他の方法でもダメ
>>> $class->expired_at = \Illuminate\Support\Facades\Date::parse('2022-10-18T15:00:00.000000Z')
=> Illuminate\Support\Carbon @1666105200 {#000
date: 2022-10-18 15:00:00.0 +00:00,
}
>>> $class->expired_at
=> Illuminate\Support\Carbon @1666072800 {#0000
date: 2022-10-18 15:00:00.0 Asia/Tokyo (+09:00),
}
実際なにをやっているか
最終的には Illuminate\Database\Eloquent\Concerns\HasAttributes::fromDateTime
で
/**
* Convert a DateTime to a storable string.
*
* @param mixed $value
* @return string|null
*/
public function fromDateTime($value)
{
return empty($value) ? $value : $this->asDateTime($value)->format(
$this->getDateFormat()
);
}
asDateTime
でタイムゾーンの情報を持ったCarbonインスタンスを生成したのにformat
でタイムゾーン落ちした文字列にしてしまっていたという話でした
>>> \Illuminate\Support\Facades\Date::parse('2022-10-18T15:00:00.000000Z')->format('Y-m-d H:i:s')
=> "2022-10-18 15:00:00"
対応
タイムゾーンが存在する場合はtzメソッドを通すことで意図した値になります
>>> \Illuminate\Support\Facades\Date::parse('2022-10-18T15:00:00.000000Z')->tz(config('app.timezone'))->format('Y-m-d H:i:s')
=> "2022-10-19 00:00:00"
Dateインスタンスを渡してもタイムゾーン落ちしてしまうので、自前で先に変換することにしました
>>> $class = new class() extends \Illuminate\Database\Eloquent\Model { protected $guarded = []; protected $casts = ['expired_at' => 'datetime']; }
=> Illuminate\Database\Eloquent\Model@anonymous {#0000}
>>> $class->expired_at = \Illuminate\Support\Facades\Date::parse('2022-10-18T15:00:00.000000Z')->tz(config('app.timezone'))->format('Y-m-d H:i:s')
=> "2022-10-19 00:00:00"
>>> $class->expired_at
=> Illuminate\Support\Carbon @1666105200 {#0000
date: 2022-10-19 00:00:00.0 Asia/Tokyo (+09:00),
}
Discussion