1. 概要
    • 確認用プログラム

      概要

      PHPで、以下のようにktime関数とdate関数を使用すると、実行のタイミングによって 求める時間とずれたUnix タイムスタンプ値になることがあります。 ここではその現象を確認するためのテストプログラムの紹介です。

      テストするのは以下のコードになります。

      mmktime(date('H'), date('i'), date('s'), date('m'), date('d'), date('Y'));

        確認用プログラム

        テストに使用したプログラムです。 テストプログラムは2つのファイルに分かれています。 実行すると、1分以内に実行が完了しますが、 求める時間とずれたUnix タイムスタンプ値になるかどうかは実行するマシンに依存します。

        • test.php
        • mktime_test.php

        実行は以下のようにtest.phpを実行します。

        $ php -f test.php
        実行すると、test.phpで指定している$processMaxの数だけプロセスが生成され、 テスト開始までの待ち時間(秒)を含んだ開始メッセージを出力します。
        start mktime test   3  now sec: 18 s   wait: 41.5 s
        start mktime test   6  now sec: 18 s   wait: 41.5 s
        start mktime test   1  now sec: 18 s   wait: 41.5 s
        start mktime test   5  now sec: 18 s   wait: 41.5 s
        ・・・

        待ち時間が過ぎてテスト開始が開始されると、各プロセスが指定回数だけ問題のコードを実行します。 現在日時より過去の値が得られた場合、エラーとして現在日時と誤った日時を表示します。

        mktime test:  84 now: 2012-11-17 17:59:59(1353142799) err: 2012-11-17 17:00:00(1353139200)
        mktime test:  25 now: 2012-11-17 17:59:59(1353142799) err: 2012-11-17 17:00:00(1353139200)

        test.php

        <?php
        $processMax = 100;
        $loopMax = 1000;
        
        $taskNumber = -1;
        for ($i = 0; $i < $processMax; $i ++) {
            $pid = pcntl_fork();
            if ($pid == 0) {
                $taskNumber = $i;
                break;
            } else if ($pid == -1) {
                die('failure fork');
            }
        }
        
        if ($pid == 0) {
            $cmd = sprintf("/usr/bin/php -f mktime_test.php %d %d &", $loopMax, $taskNumber);
            system($cmd);
        }
        

        mktime_test.php

        <?php
            
        class MktimeTest {
            const US = 1000000;
            const LOG = 'mktime_test.log';
            private $taskNumber;
        
            public function test($max, $taskNumber) {
                $this->taskNumber = $taskNumber;
                $this->wait();
                for ($i = 0; $i < $max; $i ++) {
                    $this->test_sec();
                }
                printf("end mktime test: %3d\n", $this->taskNumber);
            }
        
            public function wait() {
                $sec = date('s');
                $waitSec = (60 - $sec - 1) * self::US + self::US*0.5;
                if ($waitSec == 0) {
                    $waitSec = 59;
                } else if ($waitSec < 0) {
                    die('leap second ?');
                }
                printf("start mktime test %3d  now sec: %d s   wait: %0.1f s\n", 
                    $this->taskNumber, $sec, $waitSec/self::US);
                usleep($waitSec);
            }
        
            public function test_sec() {
                $nowTs = time();
                $testTs = mktime(date('H'), date('i'), date('s'), date('m'), date('d'), date('Y'));
                if ($nowTs > $testTs) {
                    $msg = sprintf("mktime test: %3d now: %s(%d) err: %s(%d)\n",
                                $this->taskNumber,
                                date('Y-m-d H:i:s', $nowTs), $nowTs,
                                date('Y-m-d H:i:s', $testTs), $testTs);
                    print($msg);
                    file_put_contents(self::LOG, $msg, FILE_APPEND | LOCK_EX);
                }
            }
        }
        
        if ($argc != 2 && $argc != 3) {
            die("usage:\n  php -f $argv[0] [count number] [number]\n\n");
        }
        $loopCount = $argv[1];
        if ($argc == 3) {
            $taskNumber = $argv[2];
        } else {
            $taskNumber = 0;
        }
        
        $t = new MktimeTest();
        $t->test($loopCount, $taskNumber);