php createfromformatに気を付けろ

フォーマットを変換するためにこのようなコードを書いていたのですが

$date='2017/11';
createFromFormat('Y/m',$date)->format('Ym');

一見正しそうなこのコード

これが’201712′になってしまうというおかしな挙動に見舞われました。

2018/01/31日に行うと11月は31日がないので12月1日の日付ができてしまうのだ。

つまり現在のシステム日付が29~31日の時だけに起こる。

これは実行する日によって正常であるのに特定の日だけにおこるという非常に困ったバグを引き起こすということです。

APIをよく読むと

http://php.net/manual/ja/datetime.createfromformat.php

「format に文字 ! が含まれない場合は、作成した時刻値のうち format で指定されていない部分を 現在のシステム時刻で初期化します。」 とあります。

ソースまで読んではいないけど、”format で指定されていない部分”だから月が書き換わってしまうのはおかしいように思うが この初期化というのが後でおこなわれているとすればこのようになるのではないかと思われ これがバグではなく仕様だということになると上記のコードはまずいということになる。

よく見るとUser Contributed Notes 12にあるコードがそれを回避するコードが書かれています。

テストコード

<?php

$dates = [ 
        '2016/01',
        '2016/02',
        '2016/03',
        '2016/04',
        '2016/05',
        '2016/06',
        '2016/07',
        '2016/08',
        '2016/09',
        '2016/10',
        '2016/11',
        '2016/12',
        '2017/01',
        '2017/02',
        '2017/03',
        '2017/04',
        '2017/05',
        '2017/06',
        '2017/07',
        '2017/08',
        '2017/09',
        '2017/10',
        '2017/11',
        '2017/12',
        '2018/01',
        '2018/02',
];
function chk_date($date) {
    $datetime = DateTime::createFromFormat ( 'Y/m', $date );

    echo var_export($datetime,true)."\n";

    echo $datetime->format('Ym')."\n";
}

$system = new DateTime();
echo "system->".var_export($system,true)."\n\n";
foreach ( $dates as $date ) {
    echo $date . "->\n";
    chk_date( $date );
    echo "<-\n";
}

2018/02/01 14:42に行った結果は想定通りの結果だけど

system->DateTime::__set_state(array(
   'date' => '2018-02-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))

2016/01->
DateTime::__set_state(array(
   'date' => '2016-01-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201601
<-
2016/02->
DateTime::__set_state(array(
   'date' => '2016-02-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201602
<-
2016/03->
DateTime::__set_state(array(
   'date' => '2016-03-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201603
<-
2016/04->
DateTime::__set_state(array(
   'date' => '2016-04-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201604
<-
2016/05->
DateTime::__set_state(array(
   'date' => '2016-05-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201605
<-
2016/06->
DateTime::__set_state(array(
   'date' => '2016-06-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201606
<-
2016/07->
DateTime::__set_state(array(
   'date' => '2016-07-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201607
<-
2016/08->
DateTime::__set_state(array(
   'date' => '2016-08-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201608
<-
2016/09->
DateTime::__set_state(array(
   'date' => '2016-09-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201609
<-
2016/10->
DateTime::__set_state(array(
   'date' => '2016-10-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201610
<-
2016/11->
DateTime::__set_state(array(
   'date' => '2016-11-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201611
<-
2016/12->
DateTime::__set_state(array(
   'date' => '2016-12-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201612
<-
2017/01->
DateTime::__set_state(array(
   'date' => '2017-01-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201701
<-
2017/02->
DateTime::__set_state(array(
   'date' => '2017-02-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201702
<-
2017/03->
DateTime::__set_state(array(
   'date' => '2017-03-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201703
<-
2017/04->
DateTime::__set_state(array(
   'date' => '2017-04-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201704
<-
2017/05->
DateTime::__set_state(array(
   'date' => '2017-05-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201705
<-
2017/06->
DateTime::__set_state(array(
   'date' => '2017-06-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201706
<-
2017/07->
DateTime::__set_state(array(
   'date' => '2017-07-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201707
<-
2017/08->
DateTime::__set_state(array(
   'date' => '2017-08-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201708
<-
2017/09->
DateTime::__set_state(array(
   'date' => '2017-09-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201709
<-
2017/10->
DateTime::__set_state(array(
   'date' => '2017-10-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201710
<-
2017/11->
DateTime::__set_state(array(
   'date' => '2017-11-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201711
<-
2017/12->
DateTime::__set_state(array(
   'date' => '2017-12-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201712
<-
2018/01->
DateTime::__set_state(array(
   'date' => '2018-01-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201801
<-
2018/02->
DateTime::__set_state(array(
   'date' => '2018-02-01 14:42:13.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201802
<-

システム日付を2018/01/31にしてみると

system->DateTime::__set_state(array(
   'date' => '2018-01-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))

2016/01->
DateTime::__set_state(array(
   'date' => '2016-01-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201601
<-
2016/02->
DateTime::__set_state(array(
   'date' => '2016-03-02 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201603
<-
2016/03->
DateTime::__set_state(array(
   'date' => '2016-03-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201603
<-
2016/04->
DateTime::__set_state(array(
   'date' => '2016-05-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201605
<-
2016/05->
DateTime::__set_state(array(
   'date' => '2016-05-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201605
<-
2016/06->
DateTime::__set_state(array(
   'date' => '2016-07-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201607
<-
2016/07->
DateTime::__set_state(array(
   'date' => '2016-07-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201607
<-
2016/08->
DateTime::__set_state(array(
   'date' => '2016-08-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201608
<-
2016/09->
DateTime::__set_state(array(
   'date' => '2016-10-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201610
<-
2016/10->
DateTime::__set_state(array(
   'date' => '2016-10-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201610
<-
2016/11->
DateTime::__set_state(array(
   'date' => '2016-12-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201612
<-
2016/12->
DateTime::__set_state(array(
   'date' => '2016-12-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201612
<-
2017/01->
DateTime::__set_state(array(
   'date' => '2017-01-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201701
<-
2017/02->
DateTime::__set_state(array(
   'date' => '2017-03-03 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201703
<-
2017/03->
DateTime::__set_state(array(
   'date' => '2017-03-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201703
<-
2017/04->
DateTime::__set_state(array(
   'date' => '2017-05-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201705
<-
2017/05->
DateTime::__set_state(array(
   'date' => '2017-05-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201705
<-
2017/06->
DateTime::__set_state(array(
   'date' => '2017-07-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201707
<-
2017/07->
DateTime::__set_state(array(
   'date' => '2017-07-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201707
<-
2017/08->
DateTime::__set_state(array(
   'date' => '2017-08-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201708
<-
2017/09->
DateTime::__set_state(array(
   'date' => '2017-10-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201710
<-
2017/10->
DateTime::__set_state(array(
   'date' => '2017-10-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201710
<-
2017/11->
DateTime::__set_state(array(
   'date' => '2017-12-01 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201712
<-
2017/12->
DateTime::__set_state(array(
   'date' => '2017-12-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201712
<-
2018/01->
DateTime::__set_state(array(
   'date' => '2018-01-31 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201801
<-
2018/02->
DateTime::__set_state(array(
   'date' => '2018-03-03 00:00:14.000000',
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))
201803
<-

なんてことでしょう。2/4/6/9/11のところが月が変わってしまってます

カテゴリー: php, 技術情報 タグ: ,

関連してるかも記事

この記事へのコメント

※コメントはスパム対策の為、承認制となっています。あらかじめご了承ください。

トラックバックURL