概要
PHP の DateTime クラスにて存在しない日付を表す文字列をパースしたらどうなるのかサンプルコードを書いて調べてみた。
存在しない日付の例
- 2017年12月32日 (大晦日の次の日)
- 2019年2月29日 (うるう年ではない年の2月29日)
動作確認環境
macOS Sierra + PHP 7.2
$ php --version
PHP 7.2.0 (cli) (built: Dec 3 2017 21:47:51) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
ソースコード
<?php
class MyDateTime {
private $name;
private $datetime;
private $errors;
private const FORMAT = 'Y-m-d';
private const TIME_ZONE = 'Asia/Tokyo';
public function __construct($dateString) {
$timeZone = new DateTimeZone(self::TIME_ZONE);
if (isset($dateString)) {
$this->name = $dateString;
$this->datetime = DateTime::createFromFormat(self::FORMAT, $dateString, $timeZone);
} else {
$this->name = 'now ';
$this->datetime = new DateTime(NULL, $timeZone);
}
$this->errors = DateTime::getLastErrors();
}
public function getErrors() {
if ($this->errors['warning_count'] > 0 || $this->errors['error_count'] > 0) {
return $this->errors;
} else {
return NULL;
}
}
public function __toString() {
$result = $this->name . ' -> ' . $this->datetime->format('Y-m-d H:i:s (U)');
$errorMessage = $this->getErrorMessage();
if ($errorMessage !== '') {
$result .= ' [' . $errorMessage . ']';
}
$result .= "\n";
return $result;
}
private function getErrorMessage() {
$array = [];
if ($this->errors['warning_count'] > 0) {
$array = array_merge_recursive($array, $this->errors['warnings']);
}
if ($this->errors['error_count'] > 0) {
$array = array_merge_recursive($array, $this->errors['errors']);
}
if (empty($array) ) {
return '';
} else {
return implode('/', $array);
}
}
}
$dateList = [
NULL,
'2017-12-31',
'2017-12-32', // 存在しない日
'2018-01-01',
'2019-02-29', // うるう年じゃない
'2019-03-01', // うるう年じゃない
'2020-02-29', // うるう年
'2020-03-01', // うるう年
];
foreach ($dateList as $dateString) {
$datetime = new MyDateTime($dateString);
echo $datetime;
if (!is_null($errors = $datetime->getErrors())) {
print_r($errors);
}
}
実行結果
now -> 2017-12-13 22:41:33 (1513172493)
2017-12-31 -> 2017-12-31 22:41:33 (1514727693)
2017-12-32 -> 2018-01-01 22:41:33 (1514814093) [The parsed date was invalid]
Array
(
[warning_count] => 1
[warnings] => Array
(
[10] => The parsed date was invalid
)
[error_count] => 0
[errors] => Array
(
)
)
2018-01-01 -> 2018-01-01 22:41:33 (1514814093)
2019-02-29 -> 2019-03-01 22:41:33 (1551447693) [The parsed date was invalid]
Array
(
[warning_count] => 1
[warnings] => Array
(
[10] => The parsed date was invalid
)
[error_count] => 0
[errors] => Array
(
)
)
2019-03-01 -> 2019-03-01 22:41:33 (1551447693)
2020-02-29 -> 2020-02-29 22:41:33 (1582983693)
2020-03-01 -> 2020-03-01 22:41:33 (1583070093)
- 存在しない日付の 2017-12-32 は 2018-01-01 として解釈された
- 存在しない日付の 2019-02-29 は 2019-03-01 として解釈された
パースの際にエラーは発生しないものの DateTime::getLastErrors を利用すると「The parsed date was invalid」という警告情報を取得することができる。
他に存在しない日付かどうかチェックする方法としては checkdate 関数が用意されている。
bool checkdate ( int $month , int $day , int $year )
引数で指定された日付の妥当性をチェックします。 各パラメータが適切に指定されている場合に、妥当であると判断されます。
存在しない日付をパースしたときの挙動は、Java の java.util.Calendar クラスの非厳密な解釈に似ている。
public void setLenient(boolean lenient)
日付/時間の解釈を厳密に行うかどうかを設定します。 厳密でない解釈では、「1996年2月942日」のような日付は、1996年2月1日から第941日目と同じこととみなされます。 厳密な解釈では、このような日付の場合、例外がスローされます。 デフォルトは非厳密です。