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

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

ただいまの
回答率

88.92%

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

解決済

回答 6

投稿

  • 評価
  • クリップ 2
  • VIEW 20K+

msx2

score 164

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

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

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

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

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

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

よろしくお願いします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • yuba

    2017/02/14 15:48

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

    キャンセル

  • msx2

    2017/02/14 15:50

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

    キャンセル

回答 6

+7

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

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

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

 おまけ

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

<form method="post"><input type="text" name="a" value="2017-02-14"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017/02/15"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="20170216"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017/2/17"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017020180"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017/02-19"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="xxxx-yy-zz"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="0000-00-00"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="0001-01-01"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="9999-12-31"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017-00-01"><input type="submit" value="go"></form>
<form method="post"><input type="text" name="a" value="2017-01-40"><input type="submit" value="go"></form>
$pattern='#^(\d{4})([/-]?)(\d{2})\2(\d{2})$#';
$a=!isset($_POST["a"])?NULL:filter_input(INPUT_POST,'a',FILTER_VALIDATE_REGEXP,[
  'options'=>[
    'default'=> "",
    'regexp'=> $pattern
    ],
  ]);
if(!is_null($a)){
  if(preg_match($pattern,$a,$m) and checkdate($m[3],$m[4],$m[1])){
    print date("Y-m-d",strtotime($a))."<br>";
  }else{
    print "bad data!";
  }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/16 15: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

    フォーマットチェックがある分、おかしな記述は受け付けませんが
    月末系はちゃんと処理してやる必要がありそうです。

    キャンセル

  • 2017/02/16 18:30

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

    キャンセル

  • 2017/02/16 18:34

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

    キャンセル

+4

$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 17:32

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

    キャンセル

  • 2017/02/14 17:39

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

    キャンセル

  • 2017/02/14 17:53

    b

    キャンセル

+3

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 12:17

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

    キャンセル

+3

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

<?php

$dates = [ 
        '2017028',
        '2017208'
];
function chk_date($date) {
    $formats = [ 
            'Ymd' 
    ];
    foreach ( $formats as $format ) {
        DateTime::createFromFormat ( $format, $date );
        echo "<pre>";
        print_r(date_parse_from_format ( $format ,$date ));
        echo "</pre>";
        $result = DateTime::getLastErrors ();

        if (! $result ['warning_count'] && ! $result ['error_count']) {
            return TRUE;
        }
    }
    return FALSE;
}

foreach ( $dates as $date ) {
    echo $date . ':';
    echo chk_date ( $date ) ? 'OK' : 'NG';
    echo PHP_EOL;
}
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 22:29 編集

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

    キャンセル

  • 2017/02/17 13:00

    '20172008'は通りましたよ
    [month] => 20
    [day] => 8
    PHPは5.6です

    キャンセル

  • 2017/02/17 15:11

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

    キャンセル

checkベストアンサー

0

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

<?php
$dates = [
'2017-02-28',
'2017/02/28',
'20170228',
'2016-02-29',
'1800-02-28',
'2017-02-31',
'17-02-29',
'hogehoge',
];

function chk_date($date)
{
    $formats = [
    'Y-m-d',
    'Y/m/d',
    'Ymd',
    ];
    foreach ($formats as $format){
        DateTime::createFromFormat($format, $date);
        $result = DateTime::getLastErrors();
        if(!$result['warning_count'] && !$result['error_count'])
        {
            return TRUE;
        }
    }
    return FALSE;
}

foreach ($dates as $date){
    echo $date . ':';
    echo chk_date($date)?'OK':'NG';
    echo PHP_EOL;
}


勉強になりました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 21:53

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

    キャンセル

  • 2017/02/15 14:12

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

    キャンセル

  • 2017/02/16 10:24

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

    キャンセル

  • 2017/02/16 11:22

    date さんの指摘通り、私が `d` の挙動を勘違いしていました。
    (Blog 情報は鵜呑みにしちゃダメですね。。。)

    http://php.net/manual/ja/function.date.php
    d 日。二桁の数字(先頭にゼロがつく場合も)

    ということで、許容しても良い気がしますが、厳密にやるなら`d` の取扱は要チェックです^^;

    キャンセル

-1

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 19:31

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

    キャンセル

  • 2017/02/14 19:52

    'warning_count' と 'error_count' 見ることで、OKっぽいですね。
    いやぁ、勉強になりました。

    キャンセル

  • 2017/02/14 20:17

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

    キャンセル

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

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る