記事一覧

PHPでマイクロ秒精度のDateTimeを生成する際の注意点



目次


DateTimeで現在日時を取得する

ハローみなさん、エジソンです。

PHPで日付時刻を取り扱うには、DateTimeクラスがおすすめです。

PHPで日付・時刻を操作する - チートシート

今回は、DateTimeクラスで、現在時刻を取得する方法を紹介します。

<?php
// 現在日時を生成
$now = new \DateTime();
// 出力
echo $now->format('Y-m-d H:i:s.u');
?>

2015-10-24 02:37:31.000000

出力時のフォーマットに、”年月日時分秒+マイクロ秒(小数点以下、6桁)”を指定しています。しかし、結果を見ると小数点以下が全て0になることが分かります。

DateTimeでマイクロ秒精度の現在日時を取得する

どうやら、PHPでは通常の方法で、マイクロ秒精度の情報を得ることはできないようです。では、どのような方法で、マイクロ秒精度で情報を取得すればよいでしょうか。その答えは、PHPのmicrotime関数を用いることにあります。

以下にコードを示します。

<?php
// 現在日時を生成
$now = \DateTime::createFromFormat('U.u', sprintf('%6F', microtime(true)));
// 出力
echo $now->format('Y-m-d H:i:s.u');
?>

2015-10-24 02:41:04.476676

このコードが、マイクロ秒精度で現在日時を取得するイディオムになります。上記を共通関数にてラップしておくと、何かと嬉しいことが多そうです。

microtimeの戻り値にsprintfを使うことが重要

ここからは、DateTimeでマイクロ秒精度の現在日時を取得する際に悩まされた、意味不明なエラーについての話です。

以下のコードを実行してみましょう。

<?php

ini_set('display_errors', true);
ini_set('error_reporting', E_ALL | E_STRICT);

for ($i = 1; $i < 1e6; $i++) {
	if ($i % 50000 == 0) echo $i . "\n";
	
	$foo = \DateTime::createFromFormat('U.u', \microtime(true));
	if (!($foo instanceof DateTime)) {
		echo "It failed!\ni: $i\n";
		var_dump($foo, DateTime::getLastErrors());
		exit;
	} else {
		$foo->format('Y-m-d\TH:i:s.uP');
	} //if-else
} //for

?>

すると、高確率で以下のようなエラーが発生します。

50000
It failed!
i: 69225
bool(false)
array(4) {
  ["warning_count"]=>
  int(0)
  ["warnings"]=>
  array(0) {
  }
  ["error_count"]=>
  int(1)
  ["errors"]=>
  array(1) {
    [10]=>
    string(12) "Data missing"
  }
}


なぜ、このようなことが起きてしまうのかというと、phpのバグトラッカーに答えがありました。ちなみに、上記のテストコードはバグトラッカー中に掲載されていたコードです。

Bug #60089 DateTime::createFromFormat() U after u nukes microtime

phpのバグトラッカーにはステータスが Not a bug とありました。要するにバグではないという判断なわけです。その理由は何故か?

僕は最初、sprintfを使わずに、DateTimeにそのままmicrotimeの戻り値を与えていたのですが、これが悪かったようです。microtimeの戻り値は浮動小数点です。浮動小数点というのは、桁数が多い場合に以下の表記になってしまうことがあるのです。
Wikipedia - 指数表記
コンピュータにおいては、仮数部と指数部の間に記号"e"あるいは"E"を挟む表記法もある。 (例:-1.234×10-5 = -1.234e-5) 尚、Eを用いた指数表記はJIS X 0210に規定されている。
指数表記になってしまうと、createFromFormatが正しく解釈され無いのでしょうね。そのため、sprintfを用いて、microtimeの小数点以下を6桁に収まるように整形するのです。

以上、はまり体験談でした。世界各国で困っている人が多々いたようです。タチが悪いのは、ランダムで発生する時限式のバグであるという事ですね。みなさんもお気をつけください。

関連記事

このエントリーをはてなブックマークに追加

コメント

コメントの投稿

非公開コメント

プロフィール

EZOLABブログへようこそ。
EZOLABは、札幌のソフトウェア会社です。

http://ezolab.co.jp