Carbonライブラリを使ってみた Part2〜日付の演算方法〜

加藤です。

tech.arms-soft.co.jp

前回と同じく、phpのCarbonライブラリを利用したプログラムについてです。

「ブログに投稿された記事が、2週間以内に書かれた新着のものかどうか」をチェックするプログラムを作ることになり、その時にハマったことをお話したいと思います。

また失敗談か、と思われそうですがお付き合いください。

まずやってみた

Carbonには指定した日が期間内にあるかどうかを判定してくれるbetween()メソッドが用意されているので、 それを使って「今日が『投稿日~投稿日から2週間後の日付』の間に入っているかどうかがわかればよさそうだな?」と考えて最初にコーディングしたのがこちらです。

<?php
use Carbon\Carbon;

// 記事の投稿日時と今日の日付
$created_date = Carbon::parse($created_at);
$today = Carbon::now();

// 今日が、投稿日~投稿日の2週間後の期間内か?
if($today->between($created_date,$created_date->addWeeks(2)))
{
    return true;
}
else
{
    return false;
}

$created_date にはデータベースから取得してきたブログ記事の投稿日時、 $today には今日の日付が入ります。

ここでは仮に以下の日付とします。

$created_date = Carbon::parse('2019-07-01 09:00:00');
$today = Carbon::parse('2019-07-05 09:00:00');

addWeeks()もCarbonで用意されている日付の演算のメソッドです。

$created_date->addWeeks(2)とすれば投稿から2週間後の日付を返してくれるはず。想定では2019-07-15 09:00:00。

いざ動かしてみたのですが、結果はfalse。 期間内の日付ではありません、とのことでした。失敗です。何故…。

調査した結果がこちら

いろんな箇所でdumpしまくって探った結果、無事に(?)原因が判明しました。

<?php
$created_date = Carbon::parse('2019-07-01 09:00:00');
$today = Carbon::parse('2019-07-05 09:00:00');

//  (1)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->addWeeks(2));

if($today->between($created_date,$created_date->addWeeks(2)))
{
    //  (2)-1
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->addWeeks(2).'の期間内です');
}
else
{
    // (2)-2
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->addWeeks(2).'の期間外です');
}
// (3)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->addWeeks(2));
        
exit;
// dump出力結果
// (1)
"投稿日  :2019-07-01 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-07-15 09:00:00"
// (2)-2
"投稿日の2019-07-29 09:00:00 ~ 2週間後の2019-08-12 09:00:00の期間外です"
// (3)
"投稿日  :2019-08-12 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-08-26 09:00:00"

投稿日のcreated_dateが、addWeeks()するたびに値が変わってしまっていたのでした。

(1)のdump時点では変化なしですが、(2)-2の時点では(1)のaddWeeks()と、さらにif文の中でのaddWeeks()の影響を受けてしまい、結果として4週間後の日付に。(怖)

(3)の時点では最終的に元々の投稿日から6週間後の日付になっています。

Carbonは最初にインスタンスを作ったらそのまま変わらないと勝手に思い込んでいたんですが、そうじゃなかったんですね…。(このように値が変わるオブジェクトのことをミュータブルオブジェクトと呼びます。)

ちゃんと公式ドキュメントや、先人の皆様のプログラム例を調べていたらわかったであろうことなのでだいぶ恥ずかしいですが、勉強になりました。

それでは正しいやり方を。

その1:Carbonインスタンスを複製してから演算

copy()->addWeeks(2)とすると、created_dateインスタンスを複製して計算してくれるようになり、 元々のcreated_dateに影響を与えずに実行することができました。

<?php
$created_date = Carbon::parse('2019-07-01 09:00:00');
$today = Carbon::parse('2019-07-05 09:00:00');

// (1)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->copy()->addWeeks(2));

if($today->between($created_date,$created_date->copy()->addWeeks(2)))
{
    // (2)-1   
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->copy()->addWeeks(2).'の期間内です');
}
else
{
    // (2)-2
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->copy()->addWeeks(2).'の期間外です');
}
// (3)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->copy()->addWeeks(2));

exit;
// dump出力結果
// (1)
"投稿日  :2019-07-01 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-07-15 09:00:00"
// (2)-2
"投稿日の2019-07-01 09:00:00 ~ 2週間後の2019-07-15 09:00:00の期間内です"
// (3)
"投稿日  :2019-07-01 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-07-15 09:00:00"

投稿日が変わっていないです。やりました!

その2:CarbonImmutableを使う

Carbonのバージョンが2.0以上の場合は、CarbonImmutableというイミュータブル版のCarbonが使えるようです。

こちらで宣言するとインスタンスをコピーしなくても値を保持してくれました。 https://carbon.nesbot.com/docs/

<?php
use Carbon\Carbon;
use Carbon\CarbonImmutable;     //use宣言をお忘れなく

// 投稿日時をImmutableのCarbonにパース
$created_date = CarbonImmutable::parse('2019-07-01 09:00:00');
$today = Carbon::parse('2019-07-05 09:00:00');
// (1)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->addWeeks(2));

if($today->between($created_date,$created_date->addWeeks(2)))
{
    // (2)-1
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->addWeeks(2).'の期間内です');
}
else
{
    // (2)-2
    var_dump('投稿日の'.$created_date.' ~ 2週間後の'.$created_date->addWeeks(2).'の期間外です');
}
// (3)
var_dump('投稿日  :'.$created_date);
var_dump('今日   :'.$today);
var_dump('2週間後  :'.$created_date->addWeeks(2));

exit;
// dump出力結果
// (1)
"投稿日  :2019-07-01 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-07-15 09:00:00"
// (2)-2
"投稿日の2019-07-01 09:00:00 ~ 2週間後の2019-07-15 09:00:00の期間内です"
// (3)
"投稿日  :2019-07-01 09:00:00"
"今日   :2019-07-05 09:00:00"
"2週間後  :2019-07-15 09:00:00"

先ほどと同じく、こちらも投稿日が変わっていません。

まとめ

copy()するのを忘れてしまいそうですし、Carbonのバージョンに制約がなければ最初からCarbonImmutableで宣言するのもありですね。

(Carbonのミュータブルの特性を利用してシンプルなコードを書くこともできそうなので、一概にイミュータブルがよい、とは言えないですが…。)

今回は以上です。 日付の加算・減算をするときには、気をつけます。。。