動機
PHPのDateTime型において、1ヶ月の加減算は仕様により都合がわるい。
DateTime::add
DateTime::sub
test_add.php
<?php
$datetime = new DateTime('2015-03-31');
var_dump($datetime);
$datetime->add(new DateInterval('P1M'));
var_dump($datetime);
$ php test_add.php
object(DateTime)#1 (3) {
["date"]=>
string(26) "2015-03-31 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
object(DateTime)#1 (3) {
["date"]=>
string(26) "2015-05-01 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
3/31
の1ヶ月後は4/30
になってほしい
test_sub.php
<?php
$datetime = new DateTime('2015-03-31');
var_dump($datetime);
$datetime->sub(new DateInterval('P1M'));
var_dump($datetime);
$ php test_sub.php
object(DateTime)#1 (3) {
["date"]=>
string(26) "2015-03-31 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
object(DateTime)#1 (3) {
["date"]=>
string(26) "2015-03-03 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
3/31
の1ヶ月前は2/28
になってほしい
そこで、大変野蛮なやり方であるが、MySQL
くんの力を借りてみる試験を行った。
MyDateTime.class.php
<?php
class MyDateTime
{
private static $_dbh;
private static $_datetime;
private static $_dbInfo = array(
'dbhost' => 'dbhost',
'dbuser' => 'dbuser',
'dbpass' => 'dbpass',
'dbname' => '',
'charset' => 'utf8',
'driver' => 'mysql',
'dbport' => '1234',
);
/**
* create instance guard
*
* @return void
*/
private function __construct(){}
/**
* func doc comment
*
* @return void
*/
public static function create($_string='')
{
self::_init($_string);
return self::$_datetime;
}
/**
* https://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_date-add
*
* @return void
*/
public static function add($_string='')
{
if (! empty($_string)) {
$query = "SELECT DATE_ADD('".self::$_datetime->format('Y-m-d H:i:s')."', INTERVAL ".$_string.") AS datetime";
return self::_addsub($query);
}
}
/**
* https://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_date-add
*
* @return void
*/
public static function sub($_string='')
{
if (! empty($_string)) {
$query = "SELECT DATE_SUB('".self::$_datetime->format('Y-m-d H:i:s')."', INTERVAL ".$_string.") AS datetime";
return self::_addsub($query);
}
}
/**
* func doc comment
*
* @return void
*/
private static function _addsub($query)
{
$rslt = self::$_dbh->query($query);
$row = $rslt->fetch_assoc();
$rslt->close();
return DateTime::createFromFormat('Y-m-d H:i:s', $row['datetime']);
}
/**
* function doc comment
*
* @return void
*/
private static function _connect()
{
if (self::$_dbh === null) {
self::$_dbh = new mysqli(
self::$_dbInfo['dbhost'],
self::$_dbInfo['dbuser'],
self::$_dbInfo['dbpass'],
self::$_dbInfo['dbname']
);
self::$_dbh->set_charset(self::$_dbInfo['charset']);
}
}
/**
* func doc comment
*
* @return void
*/
private static function _init($_string)
{
self::_connect();
if (empty($_string)) {
$query = "SELECT NOW() AS datetime";
} else {
$query = "SELECT DATE_FORMAT('".$_string."', '%Y-%m-%d %H:%i:%s') AS datetime";
}
$rslt = self::$_dbh->query($query);
$row = $rslt->fetch_assoc();
$rslt->close();
self::$_datetime = DateTime::createFromFormat('Y-m-d H:i:s', $row['datetime']);
}
}
test.php
<?php
require_once 'MyDateTime.class.php';
// 指定なし
$datetime = MyDateTime::create();
var_dump($datetime);
// 指定あり
$datetime = MyDateTime::create('2015-03-31');
var_dump($datetime);
// 加算
$dateadd = MyDateTime::add('1 MONTH');
var_dump($dateadd);
// 減算
$datesub = MyDateTime::sub('1 MONTH');
var_dump($datesub);
$ php test.php
object(DateTime)#4 (3) {
["date"]=>
string(26) "2015-04-27 11:34:12.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
object(DateTime)#3 (3) {
["date"]=>
string(26) "2015-03-31 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
object(DateTime)#1 (3) {
["date"]=>
string(26) "2015-04-30 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
object(DateTime)#5 (3) {
["date"]=>
string(26) "2015-02-28 00:00:00.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(10) "Asia/Tokyo"
}
うまくいった。
ご注意
上記は試験スクリプトです。セキュリティ的パフォーマンス的使い勝手的な考慮は含まれていません。
追記
Carbon
というDateTime拡張ライブラリが、便利かも。
Official: Carbon
packagist: nesbot/carbon