Laravelで日付時刻の処理を書くならCarbon(カーボン)を使え

PHPで日付時刻処理をする時にとりあえず strtotime() でタイムスタンプに戻すことから始める皆さん、こんにちは。

日付時刻をPHPで扱うのは簡単ですが、少し計算などしようとすると結構面倒です。

タイムスタンプに変えてしまえば扱いは楽ですが、いちいち変換したり足したり引いたりしたくない!もっと人間にも分かりやすい形式で扱いたい!さらには時差も考慮して扱いたいし、英語環境では「6/10/2014」のような表記にしたい。

そんな要望に応えるためにPHPのDateTimeクラスを拡張して作られた「Carbon(カーボン)」というライブラリのがあります。

A simple PHP API extension for DateTime.

単体のライブラリとしても使えますが、多くのPHPフレームワークにも採用されており、LaravelもCarbonを採用しています。

Carbonのインストール

個別にインストールする場合はComposerを使うかダウンロードして配置。
Laravelの場合は最初からパッケージに含まれてるので特別な作業は必要なし。

composer require nesbot/carbon

ファイルが適切な場所に配置されていれば以下の宣言を入れれば使えます。
Laravelの場合も自分でCarbonクラスをnewするなら必要(モデルの中身は自動的に変換される)

use Carbon\Carbon;

Carbonの簡単な使い方

use Carbon\Carbon;

// 現在日時
$now = Carbon::now();

// ロンドンの現在日時
$nowInLondonTz = Carbon::now('Europe/London');

// 特定の日付をつくり、今日は何歳か取得
$date = Carbon::createFromDate(1975, 5, 21);
$howOldAmI = $date->age;

// 今日の日時
$today = $date->today();
// 明日の日時
$yesterday = $date->yesterday();

// 日付を10日進める
$date->addDays(10);
// 日付に15日戻す
$date->subDays(15);

// 日付同士の差を取得
$date1 = Carbon::createFromDate(1975, 1, 1);
$date2 = Carbon::createFromDate(1980, 10, 1);
$date1->diffInYears($date2);   // 年数
$date1->diffInMonths($date2);  // 月数
$date1->diffInWeeks($date2);   // 週数
$date1->diffInDays($date2);    // 日数
$date1->diffInHours($date2);   // 時間数
$date1->diffInMinutes($date2); // 分数
$date1->diffInSeconds($date2); // 秒数

// フォーマットを変えて出力
echo $date->format('Y/m/d');

// タイムゾーンを変更してフォーマットも変える
echo $date->timezone('Asia/Singapore')->format('d/m/Y');

他にも色々な使い方や変換方法があるので詳しくはマニュアルをどうぞ。

適当に日付を投げ入れても比較的きちんと変換して、人間にも分かりやすい形で扱えるのでかなりオススメです。同じ結果に辿り着く方法がいくつかあるので、慣れないうちは混乱するかもしれません。

Carbonのハマりポイントとして、当然ですが addDay() などのメソッドで日付計算を行うと、元となる日付時刻データが変わるので何も考えずに使うと思わぬ所で変な動きをします。

// こうすると比較するオブジェクトが共に1月進んでしまう
$date->diffInWeeks( $date->addMonth() );

// copy()メソッドを使えばオブジェクトをコピーできる
$date->diffInWeeks( $date->copy()->addMonth() );

LaravelでCarbonを使いこなす

Laravelのはただの便利ライブラリとしての採用だけでなく、データベースのカラムの型が「date」「datetime」の場合はモデルクラスに格納される値を自動的にCarbonクラスとして変換させて使うことができます。

デフォルトでは「created_at」「updated_at」の2カラムは自動的にCarbonクラスに展開されます。

なので最終更新からの経過日数を知りたい場合は、

$diff = $user->updated_at->diffInDays( Carbon::now() );
echo $diff;

というように非常に分かりやすく扱えて人間の尊厳を守られている感じになりますね。

追加カラムをCarbonクラスとして扱う

先の2カラムは自動的に変換されますが、自分で作ったカラムもCarbon化してほしい!と思うことでしょう。ほとんど同じことですが以下の方法で変更できます。特殊なことをしない限り1番目の方法でいいです。

  • モデルのプロパティとしてカラム名を宣言する方法
  • モデルのメソッドを上書きする方法
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * @var array
     */
    protected $dates = ['birthday', 'created_at', 'updated_at', 'deleted_at'];
}

$datesプロパティを上書きしてやれば自動的に変換してくれます。

データを格納する場合(上書きする場合)も、先にCarbonオブジェクトに変換しておく必要はありません。が、「2016/1/1」というフォーマットだと弾かれるので、できる限り「2016-01-01」というMySQL標準フォーマットでやりとりする方がいいでしょう。

Laravelのアクセサとキャスト

これは標準で用意された日付時刻クラスへの変換ですが、Laravelのモデルはアクセサによる変換を助ける仕組みが用意されています。

Laravel 5.2 Eloquent: Mutators – https://laravel.com/docs/5.2/eloquent-mutators

また、Attribute Casting(属性のキャスティング)と呼ばれる指定をすることで、DB格納時に自動的に型を変換してくれる便利な機能も備わっています。

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * @var array
     */
    protected $casts = [
        'favorites' => 'array',
        'cache' => 'serialize',
    ];
}

上の例だと、favoritesカラムは配列を入れておけば保存時に自動的にカンマ区切りになり、復元時は配列として戻してくれます。cacheカラムは同じように自動でシリアライズ化してくれるという便利な機能。

特にarrayへのキャストは、カンマ区切りで保存されるのでMySQLの「FIND_IN_SET()」で検索が可能になります。正規化したり別テーブルに切り出すまでも無いデータの保存や、SQL上でどうこうする必要がない場合にも重宝するのです。