質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

6回答

31667閲覧

ユーザーの入力した文字列が日付として有効かどうかをチェックする方法

msx2

総合スコア174

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

2クリップ

投稿2017/02/14 02:38

PHPのプログラミングについての質問です。

日付を入力するフォームがあり、送信された文字列が日付として有効かどうかをチェックしています。
年月日を別々に入力させてcheckdate関数でチェックする方法が無難だと思いますが今回はできません。

現状はこんな感じで書いています

PHP

1//送信された日付文字列 2$date = $_POST('date'); 3//日付かどうかチェック 4if(strtotime($date) === false){ 5 //ダメなときの処理 6 $date = null; 7}else{ 8 //下記フォーマットに統一する 9 $date = date('Y-m-d',strtotime($date)); 10}

同じ様なチェックをDateTimeオブジェクトを使ってシンプルに書くことはできるでしょうか?

ユーザーからの入力は、YYYY-MM-DD、YYYY/MM/DD、YYYYMMDDを想定しています。

よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuba

2017/02/14 06:48

2017-02-31は拒絶しますか、受理しますか?
msx2

2017/02/14 06:50

2月31日はエラーとします
guest

回答6

0

ユーザーからの入力は、YYYY-MM-DD、YYYY/MM/DD、YYYYMMDDを想定

であればパターンを設定して正規表現でチェックしてください
そうでない場合他の想定していないパターンにもマッチします
またフォーマットがあっていればstrtotimeは無理やり日付として
解釈するので、極端な値を設定したり0月や20月とか0日とか99日などを
チェックしてもそれなりに解釈します。

PHP

1$a=["2017-02-14","2017/02/15","20170216",//問題なくマッチ 2 "2017/2/17","2017020180","2017/02-19","xxxx-yy-zz",//エラー 3 "0000-00-00","9999-99-99" //極端な数字でもマッチ 4 ]; 5/* $pattern="#^\d{4}[/-]{0,1}\d{2}[/-]{0,1}\d{2}$#"; */ 6/* おかしなパターンを拾うのでいかに修正 */ 7$pattern="#^\d{4}([/-]?)\d{2}\\1\d{2}$#"; 8foreach ($a as $val){ 9 print $val." is "; 10 if(preg_match($pattern,$val,$match)){ 11 print date("Y-m-d",strtotime($val))."<br>"; 12 }else{ 13 print "ng<br>"; 14 } 15}

おまけ

POSTで受けたデータを前提とするのであれば、filter_inputで先に
バリデートするとか、日付形式の文字かどうかをcheckdate()するとか
色々やりようはあります

HTML

1<form method="post"><input type="text" name="a" value="2017-02-14"><input type="submit" value="go"></form> 2<form method="post"><input type="text" name="a" value="2017/02/15"><input type="submit" value="go"></form> 3<form method="post"><input type="text" name="a" value="20170216"><input type="submit" value="go"></form> 4<form method="post"><input type="text" name="a" value="2017/2/17"><input type="submit" value="go"></form> 5<form method="post"><input type="text" name="a" value="2017020180"><input type="submit" value="go"></form> 6<form method="post"><input type="text" name="a" value="2017/02-19"><input type="submit" value="go"></form> 7<form method="post"><input type="text" name="a" value="xxxx-yy-zz"><input type="submit" value="go"></form> 8<form method="post"><input type="text" name="a" value="0000-00-00"><input type="submit" value="go"></form> 9<form method="post"><input type="text" name="a" value="0001-01-01"><input type="submit" value="go"></form> 10<form method="post"><input type="text" name="a" value="9999-12-31"><input type="submit" value="go"></form> 11<form method="post"><input type="text" name="a" value="2017-00-01"><input type="submit" value="go"></form> 12<form method="post"><input type="text" name="a" value="2017-01-40"><input type="submit" value="go"></form>

PHP

1$pattern='#^(\d{4})([/-]?)(\d{2})\2(\d{2})$#'; 2$a=!isset($_POST["a"])?NULL:filter_input(INPUT_POST,'a',FILTER_VALIDATE_REGEXP,[ 3 'options'=>[ 4 'default'=> "", 5 'regexp'=> $pattern 6 ], 7 ]); 8if(!is_null($a)){ 9 if(preg_match($pattern,$a,$m) and checkdate($m[3],$m[4],$m[1])){ 10 print date("Y-m-d",strtotime($a))."<br>"; 11 }else{ 12 print "bad data!"; 13 } 14}

投稿2017/02/14 02:58

編集2017/02/14 07:52
yambejp

総合スコア114784

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

msx2

2017/02/14 03:24

やはり入力形式を絞ると正規表現のチェックが必要になるんですね。 正規表現が苦手で避けていましたが少し勉強してみます。。 ありがとうございました!
KiyoshiMotoki

2017/02/14 04:08

横から失礼します。  #^\d{4}[/-]{0,1}\d{2}[/-]{0,1}\d{2}$# だと、  2017/02-14 や  201702/14 のような、区切り文字がチグハグな文字列にもマッチしてしまいます。 そのため、正規表現を使うのであれば、 以下のようにそれぞれの区切り文字で個別にチェックする方が良いと思います。  #^\d{4}/\d{2}/\d{2}$#  #^\d{4}-\d{2}-\d{2}$#  #^\d{8}$#
yambejp

2017/02/14 04:23 編集

KiyoshiMotokiさんフォローありがとうございます。 たしかにおっしゃるパターンがありますね ただしパターンはそんなに増やす必要はないので 元の文を修正しておきます
msx2

2017/02/14 06:54

まず入力の書式チェックとして正規表現で検証してから、日付としての妥当性は別途チェックするという流れですね。こうなると一連のチェック関数としてまとめた方がよさそうですね。
msx2

2017/02/14 08:00

おまけの追記ありがとうございます。 filter_inputなんて初めて見ました。 日付チェックの質問からこんなテクニックが出てくるとは、、 有益なご回答を本当にありがとうございます。
退会済みユーザー

退会済みユーザー

2017/02/16 06:49

strtotime のマニュアル見ても良く分からなかったので、 自身のスクリプトの検証と合わせて、こちらのスクリプトにも 気になる日付を試してみました。 2017/02/8 is ng 2017/02/30 is 2017-03-02 2017028 is ng 20000000 is 1999-11-30 20170230 is 2017-03-02 20170431 is 2017-05-01 2017/04/31 is 2017-05-01 フォーマットチェックがある分、おかしな記述は受け付けませんが 月末系はちゃんと処理してやる必要がありそうです。
msx2

2017/02/16 09:30

検証ありがとうございます!! dは月関係なく31まで受け入れるんですね
退会済みユーザー

退会済みユーザー

2017/02/16 09:34

strtotime を利用するなら checkdate が必要そうです。
guest

0

$d = DateTime::createFromFormat('Y-m-d', $date) or DateTime::createFromFormat('Y/m/d', $date) or DateTime::createFromFormat('Ymd', $date); if ($d === false) { $date = null; } else { $date = $d -> format('Y-m-d'); }

投稿2017/02/14 07:03

yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

msx2

2017/02/14 07:16

こんな風に書けるんですね。 書式が増えても対応できて、失敗はfalseが返ってきて、何よりもわかりやすい! ありがとうございました!!
KiyoshiMotoki

2017/02/14 07:22

横から失礼します。 $d = DateTime::createFromFormat('Y-m-d', $date) or   DateTime::createFromFormat('Y/m/d', $date) or   DateTime::createFromFormat('Ymd', $date); について、PHP の 'or' 演算子は '=' 演算子より優先順位が低いので、最も左の  DateTime::createFromFormat() の結果だけが、変数 $d に代入されることになりますよ。 http://php.net/manual/ja/language.operators.precedence.php 'or' の代わりに '||' を使うと、意図通りの動作になるかと思います。 -------------------------------------- <?php $d = false || false || true; var_dump($d); $d = false or false or true; var_dump($d); -------------------------------------- 実行結果 bool(true) bool(false)
yuba

2017/02/14 07:46

あ、そしたら||にするのでなく( )で囲ってもらうのがいいです。 || だとDateTimeオブジェクトでなくboolが出てきてしまうので。
msx2

2017/02/14 07:47

KiyoshiMotoki様 度々のフォローありがとうございます。 演算子の優先順位なんてあまり考えたことがなかったのですが、なるほどこういう事なんですね。 今までわからなかったらとりあえずカッコ()で括っていましたけど演算子の優先順位を意識することで無用な()を減らせそうです。()括りは読みやすさというメリットもありますけど。
msx2

2017/02/14 07:54

> || だとDateTimeオブジェクトでなくboolが出てきてしまうので そんな違いがあるとは!? 理由はわかりませんが勉強になります!奥が深い…
KiyoshiMotoki

2017/02/14 08:05

再び横から失礼します。 改めて確認してみたところ、 '||' と 'or' のいずれも、() で囲ったとしても (演算の評価順は意図通りになるものの) $d には boolean値が代入されてしまいました。 -------------------------------------------------------- <?php $d = (false || false || new Datetime('now', new DateTimeZone('Asia/Tokyo'))); var_dump($d); $d = (false or false or new Datetime('now', new DateTimeZone('Asia/Tokyo'))); var_dump($d); -------------------------------------------------------- 実行結果 bool(true) bool(true) 考えてみれば '||' も 'or' も実行結果として boolean を返すため、これは当然の動作ですね(^^; (すなわち、私の  「'or' の代わりに '||' を使うと、意図通りの動作になるかと思います。」  という指摘も誤りでした) http://php.net/manual/ja/language.operators.logical.php というわけで、以下のようにすると、今度こそ意図通りの結果が得られます。 しかし、ちょっと無理矢理感が否めませんね、、 -------------------------------------------------------- <?php ($d = false) || ($d = new Datetime('now', new DateTimeZone('Asia/Tokyo'))) || ($d = false); var_dump($d); ($d = false) or ($d = new Datetime('now', new DateTimeZone('Asia/Tokyo'))) or ($d = false); var_dump($d); -------------------------------------------------------- 実行結果 object(DateTime)#1 (3) { ["date"]=> string(26) "2017-02-14 17:02:03.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } object(DateTime)#1 (3) { ["date"]=> string(26) "2017-02-14 17:02:03.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" }
msx2

2017/02/14 08:31

となると回答にあるコードの最初の部分を以下に修正すればOKですね $d = DateTime::createFromFormat('Y-m-d', $date) or $d = DateTime::createFromFormat('Y/m/d', $date) or $d = DateTime::createFromFormat('Ymd', $date); 短絡評価で$dにfalse以外が入った時点で以降の式が評価されないのでどの書式がきても$dにDateTimeオブジェクトが入ることを確認しました。
yuba

2017/02/14 08:32

すみません、その通りでした。
yuba

2017/02/14 08:32

あ、msx2さんの最後のその書き方かっこいい。
msx2

2017/02/14 08:39

この回答が私のような初級者にとってわかりやすく、他の方にも役立つ情報になると思いますので回答を修正いただけるとありがたいです。 ベストアンサーにしたいですし。
guest

0

終わった後の検証作業
dについて
01 から 31 あるいは 1 から 31 とあるので最後の日付だけ通るてしまうのではないでしょうか
print_r(date_parse_from_format ( $format ,$date ));

php

1<?php 2 3$dates = [ 4 '2017028', 5 '2017208' 6]; 7function chk_date($date) { 8 $formats = [ 9 'Ymd' 10 ]; 11 foreach ( $formats as $format ) { 12 DateTime::createFromFormat ( $format, $date ); 13 echo "<pre>"; 14 print_r(date_parse_from_format ( $format ,$date )); 15 echo "</pre>"; 16 $result = DateTime::getLastErrors (); 17 18 if (! $result ['warning_count'] && ! $result ['error_count']) { 19 return TRUE; 20 } 21 } 22 return FALSE; 23} 24 25foreach ( $dates as $date ) { 26 echo $date . ':'; 27 echo chk_date ( $date ) ? 'OK' : 'NG'; 28 echo PHP_EOL; 29}
Array ( [year] => 2017 [month] => 2 [day] => 8 [hour] => [minute] => [second] => [fraction] => [warning_count] => 0 [warnings] => Array ( ) [error_count] => 0 [errors] => Array ( ) [is_localtime] => ) OK 2017208: Array ( [year] => 2017 [month] => 20 [day] => 8 [hour] => [minute] => [second] => [fraction] => [warning_count] => 1 [warnings] => Array ( [7] => The parsed date was invalid ) [error_count] => 0 [errors] => Array ( ) [is_localtime] => )

投稿2017/02/16 02:12

date

総合スコア1820

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/16 02:17

ちゃんとマニュアルにあたるべきでした^^;書いてありますね。 d 日。二桁の数字(先頭にゼロがつく場合も) http://php.net/manual/ja/function.date.php つかない場合もあるってことですね。ここの回答2回めの指摘ありがとうございます。 お恥ずかしい^^;
msx2

2017/02/16 05:16

(^o^)b
退会済みユーザー

退会済みユーザー

2017/02/16 23:57 編集

'20172008'が通るんですけど、何かわかりますか? [month] => 20 が入っています。 ===追記 失礼しました。上は誤りでした。 '20172008' は通りません。 'Ym' のフォーマットで、'201720'が '[month] => 20'で通ってしまいます。 なにかご存知であれば^^;
msx2

2017/02/17 04:00

'20172008'は通りましたよ [month] => 20 [day] => 8 PHPは5.6です
退会済みユーザー

退会済みユーザー

2017/02/17 06:11

私も 5.6 と 7.0 で試しましたが、通りませんでした。 '20172008' と 'Ymd' の組み合わせで [warning_count] => 1 が出ています。 msx2 さんの環境では出ていませんか?
guest

0

PHP DateTime
DateTimeクラスで、不正な日付フォーマットの場合にException投げるようですよ。

投稿2017/02/14 02:49

kunai

総合スコア5405

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

msx2

2017/02/14 03:17

ありがとうございます。 とりあえず入れてみて例外が発生するかどうかでチェックできますね。 ただ今回は日付として有効でない文字列(ほどんどのケースは空ですが)も想定していて、そういった入力を異常値ではなく正常な入力の一つとして取り扱いたいと考えています。
guest

0

ベストアンサー

せっかくなんで、備忘録兼ねて回答します。

php

1<?php 2$dates = [ 3'2017-02-28', 4'2017/02/28', 5'20170228', 6'2016-02-29', 7'1800-02-28', 8'2017-02-31', 9'17-02-29', 10'hogehoge', 11]; 12 13function chk_date($date) 14{ 15 $formats = [ 16 'Y-m-d', 17 'Y/m/d', 18 'Ymd', 19 ]; 20 foreach ($formats as $format){ 21 DateTime::createFromFormat($format, $date); 22 $result = DateTime::getLastErrors(); 23 if(!$result['warning_count'] && !$result['error_count']) 24 { 25 return TRUE; 26 } 27 } 28 return FALSE; 29} 30 31foreach ($dates as $date){ 32 echo $date . ':'; 33 echo chk_date($date)?'OK':'NG'; 34 echo PHP_EOL; 35}

勉強になりました。

投稿2017/02/14 11:15

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

msx2

2017/02/14 12:53

まとめていただいてありがとうございます。 PHPを始めた当初からどうするのがいいものか疑問に思っていて、今回ようやく納得のいく答えに行き着いた気がします。 ほんと何でも質問してみるものですね、とても勉強になりました。 この質問に投稿してくれた人たちのおかげです、ありがとうございました。 ベストアンサー悩みますが明日にでも付けたいと思います。
退会済みユーザー

退会済みユーザー

2017/02/15 05:12

'2017028' が `2017-02-08` として通る。。。何でだろ? '2017208', はちゃんと NG だけど。。。
退会済みユーザー

退会済みユーザー

2017/02/16 01:24

ベストアンサーは非常に光栄なのですが、要求仕様を満たしてませんorz どなたか、'2017028' が `2017-02-08` として通る件を解決していただけると幸いです。`d`の挙動が意図通りじゃないんですよねぇ。。。
退会済みユーザー

退会済みユーザー

2017/02/16 02:22

date さんの指摘通り、私が `d` の挙動を勘違いしていました。 (Blog 情報は鵜呑みにしちゃダメですね。。。) http://php.net/manual/ja/function.date.php d 日。二桁の数字(先頭にゼロがつく場合も) ということで、許容しても良い気がしますが、厳密にやるなら`d` の取扱は要チェックです^^;
guest

0

日付の正当性以前に $date = $_POST('date');が気になりますが、それは置いておいて、kunai さんの提案のように、DateTimeクラスで良くないですか?

exit しなければ、やりたいことは実現できる気がするのですが。

投稿2017/02/14 07:13

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

msx2

2017/02/14 07:18

ですね(^^ いい方法を教えてもらえましたのでDateTimeクラスを使うことにしました。
date

2017/02/14 09:35

一つ注意しないといけないのですが $date = new DateTime('2017-02-31'); 仮のこのようなデータが入ると 2017-03-03 と通ってしまうためこのような場合の対処はしないといけません
退会済みユーザー

退会済みユーザー

2017/02/14 09:51

マニュアルから FALSE が返ると思ってました。 フォーマットの正当性のみの確認なんですね。 ということは、DateTime を使用した回答は全滅ですね^^;
退会済みユーザー

退会済みユーザー

2017/02/14 10:12

幾つか手元で試してみました。 YYYY-MM-DD、YYYY/MM/DD、YYYYMMDD のみであれば、入力を正規表現で拾い、DateTime で同じ出力結果を出せるなら正しい。とかやってみましたが、それなら、素直に checkdate 使えよ!って感じでした。 あんまりきれいにならないですね^^; 指摘ありがとうございました。
msx2

2017/02/14 10:31

なんてこった(汗)確かに通ってしまいますね。 実際に試してみると、new DateTime('2017-01-32')は例外になりますが、DateTime::createFromFormat('Y-m-d','2017-01-32')は通ってしまいます。 もうあり得ない日付でもいいかなと思いつつも調べてみたらDateTimeクラスのgetLastErrorsメソッドでチェックできるみたいです。
退会済みユーザー

退会済みユーザー

2017/02/14 10:52

'warning_count' と 'error_count' 見ることで、OKっぽいですね。 いやぁ、勉強になりました。
退会済みユーザー

退会済みユーザー

2017/02/14 11:17

備忘録兼ねて、別回答作成しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問