Laravelで日付を属性キャストするときはUSTになるから気をつけろ!
経緯
画面に日付を表示するときに、y-m-d形式(ex. 2022-06-22)
で表示したかった時のことです。
データベースでは、その日付はtimestamp型で2022-06-22 00:37:37
のように格納されています。
Laravelではアクセサ(データベースから取得する時の処理)が定義できるので、フロント側で変換処理をしなくても、簡単にできるんじゃないかと調べてみたら、
属性キャスト
を見つけたので使ってみました。
※何か間違い等あればご指摘いただけると幸いです・・・!
環境
- PHP8.0.x
- Laravel9
属性キャストとは?
属性キャストは、モデルで追加のメソッドを定義することなく、アクセサやミューテタと同様の機能を提供します。定義する代わりに、モデルの$castsプロパティにより属性を一般的なデータ型に変換する便利な方法を提供します。
これでできるやん!
ということで以下のように指定してみました。
やった!できたぞ!!
と思いきや・・・?
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'created_at' => 'datetime:Y-m-d',
];
日付を超えた時のテストでエラー
上記のまま特に問題なく実装を続けていると、
夜の12時をまわった時のテストが失敗しました。
ファクトリーでデータを登録した後に、データを取得するメソッドを実行して、同じデータが取れてるよね?
というテストです。
エラー内容を見てみると、、、
// 1日ずれている
- 'created_at' => '2022-06-29'
+ 'created_at' => '2022-06-28'
デバック実行してみるとUSTになっているようでした。
なんだ、、データベースをUST設定にしちまってたか・・・?
と思い調べてみると、JST(日本標準時)でした。
あれ、おかしいな・・・?
日付の属性キャストは固定でUSTになる!
改めてドキュメントを見直すと、以下のような記載がありました。
dateとdatetimeのキャストはデフォルトで、アプリケーションのtimezone設定オプションで指定されているタイムゾーンに関わらず、日付をUTC ISO-8601の日付文字列(1986-05-28T21:05:54.000000Z)にシリアライズします。アプリケーションのtimezone設定オプションをデフォルトのUTCから変更せずに、常にこのシリアライズ形式を使用し、アプリケーションの日付をUTCタイムゾーンで保存することを強く推奨します。
なんと、、、そもそもtimezoneの設定はデフォルトをUTCにすることを推奨されているようでした。
ただ、私はJSTに設定してしまっていたので、日付の属性キャストは使えませんでした。
じゃあどうすればいいか?
serializeDateメソッドのカスタマイズかアクセサを使う
serializeDataの場合
該当モデルの全ての日付のフォーマットに影響してしまいますが、
簡単にキャストを行うにはserializeDateメソッドをオーバーライド
してカスタマイズすることです。
こちらもドキュメントに記載がありました。
/**
* 配列/JSONシリアル化の日付を準備
*
* @param \DateTimeInterface $date
* @return string
*/
protected function serializeDate(\DateTimeInterface $date)
{
return $date->format('Y-m-d');
}
この場合は、アプリケーションで設定したタイムゾーン(JST)が適用されていました。
ちなみにこの方法は、DB保存には影響しません。
アクセサの場合
特定の属性値だけ変えたい場合はアクセサを使うことができます。
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Carbon;
// Laravel9のアクセサの書き方
public function createdAt(): Attribute
{
return new Attribute(
// アクセサ
get: fn ($value) => Carbon::parse($value)->timezone('Asia/Tokyo')->format('Y-m-d')
);
}
これでJSTで日付を取得でき、テストも成功しました!
Discussion