7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel】日付処理で混乱した話

Last updated at Posted at 2024-04-22

概要

EloquentモデルのオブジェクトをtoArray()すると 日時が9時間前になってしまう

Hogeテーブル
id .. ... updated_at
1 ... ... 2021-06-24 17:50:31
2 ... ... 2021-06-24 17:50:31
3 ... ... 2021-06-24 17:50:31
コード例
$result = HogeModel::first()->toArray();
var_dump($result); // "updated_at" => "2021-06-24 17:50:31"を想定
実行結果
array(1) {
  ["updated_at"]=>
  string(27) "2021-06-24T08:50:31.000000Z" // 想定に反してUTCに変換されてしまう
}

原因

コンテナおよびLaravelのタイムゾーン設定はAsia/Tokyoなのになぜ?
Laravel 7.x以降のtoArrayメソッド が原因

解決

serializeDateメソッドをオーバーライドする

コード例
class HogeModel extends Model {
    protected function serializeDate(DateTimeInterface $date): string {
        return $date->format('Y-m-d H:i:s');
    }
}
実行結果
array(1) {
  ["updated_at"]=>
  string(19) "2021-06-24 17:50:31" // JSTのまま!
}

serializeDateメソッドを各モデルに記載するのは面倒

プロジェクト初期段階のうちにあらかじめ継承元モデルにてオーバーライドするよう合意を取っておくと吉

補足1

  • 本事象はEloquentモデル内の CREATED_AT および UPDATED_AT で指定されたカラムで発生する
    • CREATED_AT の初期値は'created_at'
    • UPDATED_AT の初期値は'updated_at'

テーブル側の'reg_date'などの表記ぶれや
Eloquentモデル側の CREATED_AT の指定もれ などに注意

補足2

  • 本事象は Eloquentモデルによるタイムスタンプ管理 を無効にすることでも対処可能
    Eloquentモデルのプロパティに下記を追加する
    public $timestamps = false;
    

$timestampsはタイムゾーン変換を防ぐことができるが日付のフォーマットは指定できない

要は継承元モデルでserializeDateをオーバーライドするのが楽

補足3

  • Eloquentモデルで$castsプロパティを指定することでフォーマットが可能
  • しかし CREATED_AT などに指定されているカラムはUTC変換の対象になってしまう
    • 下記フォーマット後にtoArray()が実行されてしまうため
    protected $casts = [
        'created_at' => 'datetime:Y/m/d H:i:s'
    ];
    

serializeDateで指定されたフォーマットのほうが優先度高い

やはりserializeDateを使うほうが手っ取り早い

付録

  • なぜUTCに変換されるのか、実際の実装を見てみる
  • serializeDateメソッドをオーバーライドしなかった場合下記処理が実行される
\Illuminate\Database\Eloquent\Concerns\HasAttributes
/**
 * Prepare a date for array / JSON serialization.
 *
 * @param  \DateTimeInterface  $date
 * @return string
 */
protected function serializeDate(DateTimeInterface $date)
{
    return Carbon::instance($date)->toJSON();
}
\Carbon\Traits\Converter.php
    /**
     * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone.
     *
     * @example
     * ```
     * echo Carbon::now('America/Toronto')->toJSON();
     * ```
     *
     * @return null|string
     */
    public function toJSON()
    {
        return $this->toISOString();
    }
    /**
     * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept:
     * 1977-04-22T01:00:00-05:00).
     *
     * @example
     * ```
     * echo Carbon::now('America/Toronto')->toISOString() . "\n";
     * echo Carbon::now('America/Toronto')->toISOString(true) . "\n";
     * ```
     *
     * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC.
     *
     * @return null|string
     */
    public function toISOString($keepOffset = false)
    {
        if (!$this->isValid()) {
            return null;
        }

        $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY';
        $tzFormat = $keepOffset ? 'Z' : '[Z]';
        $date = $keepOffset ? $this : $this->avoidMutation()->utc();

        return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat");
    }
7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?