【PHP】正確に日付チェックをする方法(年月日のみチェック、年月日時分秒のみチェック etc)
-
カテゴリ:
- PHP
今回はPHPの日付形式のバリデーションチェックの方法について説明します。
さらに、自分が実際に日付のチェックを実装していてドツボにハマったことがあったので、今後はこのようなことが起こらないように内容をここにメモしておきます。
全体として話が長くなっていますので、最初に結果的に出来あがったサンプルソースを提示しておきます。詳しく中身を知りたい人は以下の記事を読んでみてください。
日付形式のバリデーションチェックのサンプル
<?php
class DateFormat {
function check($date, $format = null) {
try {
// 桁数チェック
if ($format === 'Y-m-d') {
//年月日のチェック
if (strlen($date) > 10) return "error\r\n";
} else if ($format === 'Y-m-d H:i:s') {
//年月日時分秒のチェック
if (strlen($date) <= 10) return "error\r\n";
} else {
$format = 'Y-m-d H:i:s';
}
// 日付形式のチェック
if (strlen($date) === 0 ) {
// 空文字の場合エラー
return "error\r\n";
} else if (strlen($date) <= 6 || preg_match("/[a-zA-Z]/",$date)) {
// 文字列が膨れまれる、6文字以下、または最高文字数を超える場合はエラー
return "error\r\n";
} else {
//日付形式にフォーマットする
$format_date = new \DateTime($date);
$format_date->format($format);
}
return "ok!\r\n";
} catch (\Exception $e) {
return "error\r\n";
}
}
}
?>
日付のバリデーションチェックについて
PHPでよく見かける日付型のバリデーションチェックとしては
- strptime関数を使う方法
- checkdate関数を使う方法
- preg_match関数と正規表現を使う方法
正規表現の例: /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/
などがりますが、自分が特にオススメする方法としては、上記の以外の「Datetimeクラスのformat関数」を使う方法です。
理由としてはいくつかありますが、ざっと挙げると以下の通りになります。
- 閏年を考慮できる
- 年月の0埋め
- ハイフン(‐)やスラッシュ(/)を補完できる
- 文字列型の日付を引数に使える
- 引数を渡す際、年月日をそれぞれ分解する必要がなくそのまま渡せる
まずDatetime::formatについて、詳しく動きを説明していきます。
DateTime::formatの使い方
DateTime::format関数の機能を一言でいうと、指定した書式でフォーマットした日付を返してくれる関数です。
format関数自体は日付形式のチェックをするためのものではなく、あくまでフォーマット処理するための関数ですが、うまいこと使うことでバリデーションチェックに利用することができます。
以下のサンプルで動きを見てみましょう。
<?php
date_default_timezone_set('Asia/Tokyo');
$format_date=new DateTime($date);
echo $format_date->format($format). "\r\n";
使い方は簡単でDateTime関数のインスタンス作成時に対象の日付($date)を引数に渡し、その後format関数の引数にフォーマットしたい書式($format)を渡すことで、変換された日付が返ってくる作りになっています。
実際に動かした結果は以下になります。
<?php
date_default_timezone_set('Asia/Tokyo');
$date = '2019/5/1 23:00:00';
$format = 'Y-m-d H:i:s'
$format_date=new DateTime($date);
echo $format_date->format($format). "\r\n";
// 結果:2019-05-01 23:00:00
書式を「Y-m-d H:i:s」と指定して、日付「2019/5/1 23:00:00」をフォーマット処理しています。結果、年月日のスラッシュがハイフンになり、さらに年月が0埋めされた形に変換されているのが分かるかと思います。
他にもいろいろなパターンで試してみました。
// ハイフンやフラッシュ、スペース、コロンが無くても補完してくれる
$date:'2019/05/22 20:30:40'、$format:'Y-m-d H:i:s'の場合
結果 -> 2019-05-22 20:30:40
$date:'2019-05-2220:30:40'、$format:'Y-m-d H:i:s'の場合
結果 -> 2019-05-22 20:30:40
$date:'2019-05-22203040'、$format:'Y-m-d H:i:s'の場合
結果 -> 2019-05-22 20:30:40
$date:'20190522203040'、$format:'Y-m-d H:i:s'の場合
結果 -> 2019-05-22 20:30:40
// 時分秒(H:i:s)が無くても「00:00:00」を付与してくれる
$date:'2019-05-22'、$format:'Y-m-d H:i:s'の場合
結果 -> 2019-05-22 00:00:00
// 時分秒を切り取ってくれる
$date:'2019-05-22 20:30:40'、$format:'Y-m-d'の場合
結果 -> 2019-05-22
ある程度補完してフォーマットしてくれるのでとても便利です。
次に!ここからがバリデーションチェックの話になるのですが、例えば日付($date)に文字列を渡した際は、例外処理としてエラーになります。
<?php
date_default_timezone_set('Asia/Tokyo');
$date = 'TEST';
$format = 'Y-m-d H:i:s'
$format_date=new DateTime($date);
echo $format_date->format($format). "\r\n";
// 結果:例外エラーになる
PHP Fatal error: Uncaught exception 'Exception' with message 'DateTime::__construct(): Failed to parse time string (TEST) at position 0 (/): Unexpected character' in *****.php
つまり、format関数を使うことで指定した書式にフォーマットしつつ、例外エラーが発生するか否かでバリデーションチェックをすることが可能です。
エラーになるパターンは文字列以外にも、13月とか25時、61分など存在しない日時が渡された際にも例外処理になります。
// 存在しない日付や閏年のチェックなどまでしてくれるのが非常に便利なところ!
$date:'2019/13/02'、$format:の場合
結果 -> 例外エラー
$date:'2019/05/02 23:61:00'、$format:'Y-m-d H:i:s'の場合
結果 -> 例外エラー
$date:'2019/05/02 23:00:61'、$format:'Y-m-d H:i:s'の場合
結果 -> 例外エラー
$date:'2019/05/33 23:00:00'、$format:'Y-m-d H:i:s'の場合
結果 -> 例外エラー
$date:'2019/14/02 23:00:00'、$format:'Y-m-d H:i:s'の場合
結果 -> 例外エラー
ただし、「24時」と「60分」と「60秒」の3つは、日付や時間が繰り越されるので注意が必要です。
$date:'2019/12/31 24:30:59'、$format:'Y-m-d H:i:s'の場合
結果→ 2020-01-01 00:30:59
$date:'2019/12/31 22:60:00'、$format:'Y-m-d H:i:s'の場合
結果→ 2019-12-31 24:00:00
$date:'2019/12/31 23:30:60'、$format:'Y-m-d H:i:s'の場合
結果→ 2019-12-31 23:31:00
DateTime::formatでハマったポイント
DateTime::formatの使い方を踏まえて、最初下記のようなサンプルを作成しました。
<?php
class DateFormat {
function check($date, $format) {
try {
// 日付形式のチェック
if (strlen($date) === 0 ) {
// 空文字の場合エラー
return "error\r\n";
} else if (strlen($date) <= 6 || preg_match("/[a-zA-Z]/",$date)) {
// 文字列が膨れまれる、6文字以下、または最高文字数を超える場合はエラー
return "error\r\n";
} else {
//日付形式にフォーマットする
$format_date = new \DateTime($date);
$format_date->format($format);
}
return "ok!\r\n";
} catch (\Exception $e) {
return "error\r\n";
}
}
}
一件問題なさそうですが、1点罠がありました。
それは「年月日時分秒(Y-m-d H:i:s)のみ許容」したい場合や、逆に「年月日(Y-m-d)のみ許容」するチェックが出来ないことです。
$d = new DateFormat();
echo $d->check('2019/05/22 20:30:40', 'Y-m-d');
// 結果 -> ok!
// 日付は年月日時分秒だが、フォーマットは年月日なのにも関わらず、エラーにならい
format関数だけでバリデーションチェックをしようとした弊害というか、format関数はあくまでフォーマット処理をするためのものなので、日付型のチェックでしか使えません。
自分がハマった際は、上記のチェック不足のせいで「年月日」のカラムに「年月日時分秒」を登録してしまい、データベースのエラーが発生してしまいました。
このケースを対処するには、時分秒を許容するか否かチェックする必要があります。
最終的に行き着いたのが、桁数をチェックする処理を追加する方法です。Y-m-dの場合は10桁未満か、またY-m-d H:i:sの場合は10以上かで判別しています。
<?php
class DateFormat {
function check($date, $format = null) {
try {
// 桁数チェック
if ($format === 'Y-m-d') {
//年月日のチェック
if (strlen($date) > 10) return "error\r\n";
} else if ($format === 'Y-m-d H:i:s') {
//年月日時分秒のチェック
if (strlen($date) <= 10) return "error\r\n";
} else {
$format = 'Y-m-d H:i:s';
}
// 日付形式のチェック
if (strlen($date) === 0 ) {
// 空文字の場合エラー
return "error\r\n";
} else if (strlen($date) <= 6 || preg_match("/[a-zA-Z]/",$date)) {
// 文字列が膨れまれる、6文字以下、または最高文字数を超える場合はエラー
return "error\r\n";
} else {
//日付形式にフォーマットする
$format_date = new \DateTime($date);
$format_date->format($format);
}
return "ok!\r\n";
} catch (\Exception $e) {
return "error\r\n";
}
}
}
?>
実行結果は以下の通り。
<?php
$d = new DateFormat();
echo $d->check('2019/05/22 20:30:40');
結果→ ok!
echo $d->check('2019/05/22 20:30:40', 'Y-m-d');
結果→ error
echo $d->check('2019/05/22 20:30:40', 'Y-m-d H:i:s');
結果→ ok!
echo $d->check('2019/05/22');
結果→ ok!
echo $d->check('2019/05/22', 'Y-m-d');
結果→ ok!
echo $d->check('2019/05/22', 'Y-m-d H:i:s');
結果→ error
?>
参考になれば幸いです。^^