HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

DateTimeクラスの罠

PHPにDateTimeクラスとDateIntervalクラスなる便利なものがあると知って、喜び勇んで‥

こんな感じで、日時の加算や減算のテストプログラムを作ってみた。

  • DateTimeTest.php
<?php

class DateTimeTest {
    public static function test($date1, $date2, $target = null) {
        $intervals = array(
            '' => new DateInterval('P1Y'),
            '' => new DateInterval('P1M'),
            '' => new DateInterval('P1W'),
            '' => new DateInterval('P1D'),
            '' => new DateInterval('PT1H'),
            '' => new DateInterval('PT1M'),
            '' => new DateInterval('PT1S'),
        );
        if (!$target) {
            $target = array_keys($intervals);
        }

        printf("     <add>                         <sub>\n");
        foreach ($target as $k) {
            if (isset($intervals[$k])) {
                $interval = $intervals[$k];

                printf("%s===%s===%s\n", $k, $interval->format('%Y-%M-%D %H:%I:%S'), $k);

                printf("%5s%20s|%5s%20s\n", "", $date1->format('Y-m-d H:i:s(D)'), "", $date2->format('Y-m-d H:i:s(D)'));
                $date1->add($interval);
                $date2->sub($interval);
                printf("  => %20s|  => %20s\n" , $date1->format('Y-m-d H:i:s(D)') , $date2->format('Y-m-d H:i:s(D)'));
            }
        }
    }
}
<?php

date_default_timezone_set('Asia/Tokyo');

require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DateTimeTest.php');

DateTimeTest::test(new DateTime(), new DateTime());

要は、指定したフィールドの加算・減算を指定した分だけ行うというもの。

いざ実行。

$ php test1.php
     <add>                         <sub>
年===01-00-00 00:00:00===年
     2015-07-09 06:05:35(Thu)|     2015-07-09 06:05:35(Thu)
  => 2016-07-09 06:05:35(Sat)|  => 2014-07-09 06:05:35(Wed)
月===00-01-00 00:00:00===月
     2016-07-09 06:05:35(Sat)|     2014-07-09 06:05:35(Wed)
  => 2016-08-09 06:05:35(Tue)|  => 2014-06-09 06:05:35(Mon)
週===00-00-07 00:00:00===週
     2016-08-09 06:05:35(Tue)|     2014-06-09 06:05:35(Mon)
  => 2016-08-16 06:05:35(Tue)|  => 2014-06-02 06:05:35(Mon)
日===00-00-01 00:00:00===日
     2016-08-16 06:05:35(Tue)|     2014-06-02 06:05:35(Mon)
  => 2016-08-17 06:05:35(Wed)|  => 2014-06-01 06:05:35(Sun)
時===00-00-00 01:00:00===時
     2016-08-17 06:05:35(Wed)|     2014-06-01 06:05:35(Sun)
  => 2016-08-17 07:05:35(Wed)|  => 2014-06-01 05:05:35(Sun)
分===00-00-00 00:01:00===分
     2016-08-17 07:05:35(Wed)|     2014-06-01 05:05:35(Sun)
  => 2016-08-17 07:06:35(Wed)|  => 2014-06-01 05:04:35(Sun)
秒===00-00-00 00:00:01===秒
     2016-08-17 07:06:35(Wed)|     2014-06-01 05:04:35(Sun)
  => 2016-08-17 07:06:36(Wed)|  => 2014-06-01 05:04:34(Sun)
$ 

おぉ、これは便利。

‥と思って使っていたのだが、思わぬ罠が待っていた‥

<?php

date_default_timezone_set('Asia/Tokyo');

require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DateTimeTest.php');

DateTimeTest::test(new DateTime('2015-01-31'), new DateTime('2015-01-31'),
        array('', '', '', '', '', '', '', '', '', '', '', ''));

要は、「1月末」に1ヶ月ずつ加算・減算をしていくわけだが‥

$ php test2.php
     <add>                         <sub>
月===00-01-00 00:00:00===月
     2015-01-31 00:00:00(Sat)|     2015-01-31 00:00:00(Sat)
  => 2015-03-03 00:00:00(Tue)|  => 2014-12-31 00:00:00(Wed)
月===00-01-00 00:00:00===月
     2015-03-03 00:00:00(Tue)|     2014-12-31 00:00:00(Wed)
  => 2015-04-03 00:00:00(Fri)|  => 2014-12-01 00:00:00(Mon)
月===00-01-00 00:00:00===月
     2015-04-03 00:00:00(Fri)|     2014-12-01 00:00:00(Mon)
  => 2015-05-03 00:00:00(Sun)|  => 2014-11-01 00:00:00(Sat)
月===00-01-00 00:00:00===月
     2015-05-03 00:00:00(Sun)|     2014-11-01 00:00:00(Sat)
  => 2015-06-03 00:00:00(Wed)|  => 2014-10-01 00:00:00(Wed)
月===00-01-00 00:00:00===月
     2015-06-03 00:00:00(Wed)|     2014-10-01 00:00:00(Wed)
  => 2015-07-03 00:00:00(Fri)|  => 2014-09-01 00:00:00(Mon)
月===00-01-00 00:00:00===月
     2015-07-03 00:00:00(Fri)|     2014-09-01 00:00:00(Mon)
  => 2015-08-03 00:00:00(Mon)|  => 2014-08-01 00:00:00(Fri)
月===00-01-00 00:00:00===月
     2015-08-03 00:00:00(Mon)|     2014-08-01 00:00:00(Fri)
  => 2015-09-03 00:00:00(Thu)|  => 2014-07-01 00:00:00(Tue)
月===00-01-00 00:00:00===月
     2015-09-03 00:00:00(Thu)|     2014-07-01 00:00:00(Tue)
  => 2015-10-03 00:00:00(Sat)|  => 2014-06-01 00:00:00(Sun)
月===00-01-00 00:00:00===月
     2015-10-03 00:00:00(Sat)|     2014-06-01 00:00:00(Sun)
  => 2015-11-03 00:00:00(Tue)|  => 2014-05-01 00:00:00(Thu)
月===00-01-00 00:00:00===月
     2015-11-03 00:00:00(Tue)|     2014-05-01 00:00:00(Thu)
  => 2015-12-03 00:00:00(Thu)|  => 2014-04-01 00:00:00(Tue)
月===00-01-00 00:00:00===月
     2015-12-03 00:00:00(Thu)|     2014-04-01 00:00:00(Tue)
  => 2016-01-03 00:00:00(Sun)|  => 2014-03-01 00:00:00(Sat)
月===00-01-00 00:00:00===月
     2016-01-03 00:00:00(Sun)|     2014-03-01 00:00:00(Sat)
  => 2016-02-03 00:00:00(Wed)|  => 2014-02-01 00:00:00(Sat)
$ 

「2015-01-31」に「1ヶ月」を足したところで既に残念な結果「2015-03-03」になっている。

「2014-12-31」から「1ヶ月」を引いたところも残念な結果「2014-12-01」になっている。

まぁ、単純に処理したら「2015-02-31⇒2015-03-03」で分からんこともないんだが、各月の日数が違うはずなのにその後の処理が問題なく進行しているところを見ると、単純な文字列処理しかしていないんだという事が分かる。

結局、現場(何処)では、「月」が変わるまでループで「日」を加算・減算をするなどというアホなことを一部やっているが、どうにかしたいものだ‥ (ループの回数は、「月」が変わるまでループで「週」を加算・減算して、その後に「月」が戻る直前まで「日」を引いたほうが平均的には少なくなる‥のか(そういう問題じゃない))