PHPのDateTimeを拡張して月末の月の加減算問題に対応する

2013年11月22日

PHP

わりと有名な話ですが、PHP の DateTime クラスや日付操作関数は、月末で月の加減算を行うと期待した日付が得られないことがあります。たとえば、3月31日の1ヶ月後は5月1日、5月31日の1ヶ月前が5月1日、といった結果になります。この問題に対応するために DateTime クラスを拡張して月の加減算メソッドを作成してみました。

動作確認環境

  • PHP5.4

目次

  1. DateTime を継承して月の加減算メソッドを追加
  2. 使い方
  3. テスト

DateTime を継承して月の加減算メソッドを追加

DateTime クラスを拡張して月末問題に対応した月の加減算メソッドを追加しました。

<?php
namespace Kara\Util;

class DateTime extends \DateTime
{
    /**
     * @param int $month
     * @return $this
     */
    public function nextMonth($month = 1)
    {
        if (is_null($month)) {
            $month = 1;
        }
        $day = $this->format('j');
        $dayOfNextMonth = $this->add(new \DateInterval('P' . $month . 'M'))->format('j');
        if ($day != $dayOfNextMonth) {
            $this->sub(new \DateInterval('P' . $dayOfNextMonth . 'D'));
        }
        return $this;
    }

    /**
     * @param int $month
     * @return $this
     */
    public function lastMonth($month = 1)
    {
        if (is_null($month)) {
            $month = 1;
        }
        $day = $this->format('j');
        $dayOfLastMonth = $this->sub(new \DateInterval('P' . $month . 'M'))->format('j');
        if ($day != $dayOfLastMonth) {
            $this->sub(new \DateInterval('P' . $dayOfLastMonth . 'D'));
        }
        return $this;
    }
}

使い方

DateTime と同じように使います。

<?php
namespace Kara;
use Kara\Util\DateTime;

//月の加算
$datetime = new DateTime('2013-03-31');
$datetime->nextMonth(1);
echo $datetime->format('Y-m-d') . "\n";

//月の減算
$datetime = new DateTime('2013-05-31');
$datetime->lastMonth(1);

echo $datetime->format('Y-m-d') . "\n";

実行結果

#2013年3月31日の一ヶ月後
2013-04-30
#2013年5月31日の一ヶ月前
2013-04-30

テスト

月初、中日、月末、うるう年などテストしています。

<?php
namespace Kara\Tests\Util;
use Kara\Util\DateTime;

class DateTimeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @test
     * @dataProvider nextMonthProvider
     */
    public function nextMonth($initialize, $month, $expected)
    {
        $datetime = new DateTime($initialize);
        $datetime->nextMonth($month);
        $this->assertEquals($expected, $datetime->format('Y-m-d'));
    }

    /**
     * dataProvider for nextMonth()
     */
    public function nextMonthProvider()
    {
        return array(
            array('2013-11-01', null, '2013-12-01'),
            array('2013-11-15', null, '2013-12-15'),
            array('2013-01-27', null, '2013-02-27'),
            array('2013-01-28', null, '2013-02-28'),
            array('2013-01-29', null, '2013-02-28'),
            array('2013-01-30', null, '2013-02-28'),
            array('2013-01-31', null, '2013-02-28'),
            array('2012-01-31', null, '2012-02-29'),
            array('2013-02-28', null, '2013-03-28'),
            array('2013-03-30', null, '2013-04-30'),
            array('2013-11-01', 2, '2014-01-01'),
            array('2013-11-15', 2, '2014-01-15'),
            array('2013-01-28', 2, '2013-03-28'),
            array('2012-12-31', 2, '2013-02-28'),
            array('2011-12-31', 2, '2012-02-29'),
            array('2013-01-31', 2, '2013-03-31'),
        );
    }

    /**
     * @test
     * @dataProvider lastMonthProvider
     */
    public function lastMonth($initialize, $month, $expected)
    {
        $datetime = new DateTime($initialize);
        $datetime->lastMonth($month);
        $this->assertEquals($expected, $datetime->format('Y-m-d'));
    }

    /**
     * dataProvider for nextMonth()
     */
    public function lastMonthProvider()
    {
        return array(
            array('2013-03-01', null, '2013-02-01'),
            array('2013-03-15', null, '2013-02-15'),
            array('2013-03-28', null, '2013-02-28'),
            array('2013-03-29', null, '2013-02-28'),
            array('2013-03-30', null, '2013-02-28'),
            array('2013-03-31', null, '2013-02-28'),
            array('2012-03-31', null, '2012-02-29'),
            array('2013-04-30', null, '2013-03-30'),
            array('2013-03-01', 2, '2013-01-01'),
            array('2013-03-15', 2, '2013-01-15'),
            array('2013-03-31', 2, '2013-01-31'),
            array('2013-02-25', 2, '2012-12-25'),
            array('2013-02-28', 2, '2012-12-28'),
            array('2013-04-30', 2, '2013-02-28'),
        );
    }
}

終わりに

年や日の加減算メソッドも作ると便利だと思います。どんどん拡張しましょう。

-技術ブログ
-