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

結論

serializeDateメソッドをオーバーライドする
https://laravel.com/docs/11.x/eloquent-serialization#date-serialization

コード例
class HogeModel extends Model {
    protected function serializeDate(DateTimeInterface $date): string {
        return $date->format('Y-m-d H:i:s');
    }
}

具体例

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をオーバーライドすることでtoArray()しても日付情報をJSTのまま取得できる

コード例
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のまま!
}

プロジェクト初期のうちに継承元モデルにてオーバーライドすることで保守性を担保できる

補足1

本事象はEloquentモデル内の CREATED_AT および UPDATED_AT で指定されたカラムにて発生する

  • CREATED_AT プロパティの初期値(デフォルトカラム名)は 'created_at'
  • UPDATED_AT プロパティの初期値(デフォルトカラム名)は 'updated_at'

たとえば作成日時のカラム名を 'reg_date' としている場合
Eloquentモデルにて CREATED_AT = 'reg_date' と設定する必要がある

補足2

  • タイムゾーンの自動変換を防止するのみであれば Eloquentモデルによるタイムスタンプ管理 を無効にすることでも対処可能
  • Eloquentモデルのプロパティに下記を追加する
    public $timestamps = false;
    

$timestampsのみでは日付のフォーマットを指定することはできない

補足3

  • フォーマットを指定するのみであれば Eloquentモデルで$castsプロパティを利用することでも対処可能
    protected $casts = [
        'created_at' => 'datetime:Y/m/d H:i:s'
    ];
    
  • CREATED_ATUPDATED_AT に指定されているカラムはタイムゾーン変換の対象になってしまう
    • $castsによるフォーマット後にtoArray()が実行されてしまうため
  • また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?