たとえば、内部的にDateTimeオブジェクトなどを使って日時を扱っているとき、日時や日付を表す値を文字列として受け取ったとき、どのように検証(バリデーション)していますか?
フレームワークによっては、フォームからの入力値を自動的に検証して、DateTimeオブジェクトに変換してくれるものもありますが、たとえば、GETパラメータで受け取ったときや、CSVを使って一括で処理する場合などには、より柔軟なフォーマット定義が必要な場合があります。
$dateTime = new \DateTime('hoge');
// Exception: DateTime::__construct(): Failed to parse time string (hoge)
私の場合は、次のような日付文字列を検証するためのメソッドを作成しました。
DateTimeUtil.php
class DateTimeUtil
{
/**
* 日時文字列を DateTime オブジェクトに変換
*
* @param string $format 日付フォーマット
* @param string $string 日付文字列
* @return \DateTime|null
*/
public static function convertStringToDateTime($format, $string)
{
$dateTime = \DateTime::createFromFormat($format, $string);
if (!$dateTime instanceof \DateTime || $dateTime->format($format) !== $string || $dateTime->getTimestamp() < 0) {
return null;
}
return $dateTime;
}
/**
* 日付文字列を検証します
*
* @param string $string
* @param array $formats
* @return bool
*/
public static function validateDateString($string, $formats = array())
{
if (empty($formats)) {
$formats = array(
'Y-m-d', // 2012-01-01
'Y/m/d', // 2012/01/01
'Y/n/j', // 2012/1/1
'Y年n月j日', // 2012年1月1日
);
}
foreach ($formats as $format) {
$datetime = self::convertStringToDateTime($format, $string);
if ($datetime instanceof \DateTime) {
return true;
}
}
return false;
}
/**
* 日時文字列を検証します
* @param string $string
* @param array $formats
* @return bool
*/
public static function validateDateTimeString($string, $formats = array())
{
if (empty($formats)) {
$formats = array(
'Y-m-d H:i:s', // 2012-01-01 01:01:01
'Y/m/d H:i:s', // 2012/01/01 01:01:01
'Y/n/j G:i:s', // 2012/1/1 1:01:01
'Y-m-d\TH:i:sP', // 2012-01-01T01:01:01+00:00
);
}
foreach ($formats as $format) {
$datetime = self::convertStringToDateTime($format, $string);
if ($datetime instanceof \DateTime) {
return true;
}
}
return false;
}
}
DateTimeUtilTest.php
class StringUtilTest extends \PHPUnit_Framework_TestCase
{
public function testValidateDateString()
{
// 正常系
$this->assertEquals(StringUtil::validateDateString('2014-01-01'), true);
$this->assertEquals(StringUtil::validateDateString('2014/01/01'), true);
$this->assertEquals(StringUtil::validateDateString('2014/1/1'), true);
$this->assertEquals(StringUtil::validateDateString('2014年1月1日'), true);
// 不正なフォーマット
$this->assertEquals(StringUtil::validateDateTimeString('2014/01/01', array('Y/n/j')), false);
// 不正な年
$this->assertEquals(StringUtil::validateDateString('/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('0/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('1/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('10/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('100/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('20a1/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('0100/1/1'), false);
$this->assertEquals(StringUtil::validateDateString('20121/1/1'), false);
// 不正な月
$this->assertEquals(StringUtil::validateDateString('2012//1'), false);
$this->assertEquals(StringUtil::validateDateString('2012/0/1'), false);
$this->assertEquals(StringUtil::validateDateString('2012/13/1'), false);
$this->assertEquals(StringUtil::validateDateString('2012/100/1'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1a/1'), false);
$this->assertEquals(StringUtil::validateDateString('2012/01/1'), false);
// 不正な日
$this->assertEquals(StringUtil::validateDateString('2012/1/'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1/0'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1/01'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1/32'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1/1a'), false);
$this->assertEquals(StringUtil::validateDateString('2012/1/100'), false);
// うるう年の判定
$this->assertEquals(StringUtil::validateDateString('2012/2/29'), true);
$this->assertEquals(StringUtil::validateDateString('2014/2/29'), false);
}
public function testValidateDateTimeString()
{
// 正常系
$this->assertEquals(StringUtil::validateDateTimeString('2014-01-01 01:01:01'), true);
$this->assertEquals(StringUtil::validateDateTimeString('2014/01/01 01:01:01'), true);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:01:01'), true);
$this->assertEquals(StringUtil::validateDateTimeString('2014-01-01T01:01:01+00:00'), true);
// 不正なフォーマット
$this->assertEquals(StringUtil::validateDateTimeString('2014-01-01T01:01:01+00:00', array('Y/n/j G:i:s')), false);
// 不正な時
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 25:00:00'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 100:00:00'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 :00:00'), false);
// 不正な分
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:60:00'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:100:00'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1::00'), false);
// 不正な秒
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:01'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:01:60'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:01:100'), false);
$this->assertEquals(StringUtil::validateDateTimeString('2014/1/1 1:01:'), false);
}
}
ただ、もっといい方法があるような気が…