表題の通りなのだけど下記のコードはerrorになる。
<?php $RFC3339Nano_format = '2019-12-24T19:00:30.123456789+09:00'; $datetime_object = new DateTime($RFC3339Nano_format);
実行結果はこのとおり。
$ php test.php PHP Fatal error: Uncaught Exception: DateTime::__construct(): Failed to parse time string (2019-12-24T19:00:30.679012412+09:00) at position 20 (6): Double date specification in /tmp/test.php on line 3
これはPHPのDatetimeのコンストラクタの中の処理が8桁までしかサポートしてないために発生する。
なので 2019-12-24T19:00:30.12345678+09:00
と一桁削ってやると正しく動作する。
なので8桁よりも多い桁数のデータに対応したい場合は正規表現などで桁数を削るか、microsecまでにするなど渡す側のフォーマットを変える必要がある。
では9桁でデータが渡されるパターンはどんなケースがあるだろうか?
例えばGoの time.Time
などが該当する。
ではGoから受け取るようなケースがあるだろうか?
最近作った Songmu/horenso
から実行時間を受け取る時、まさに前述のような状況になる。
ドハマリして調べたら公式ドキュメントに書いてあった
But "u" can only parse microseconds up to 6 digits, but some language (like Go) return more than 6 digits for the microseconds, e.g.: "2017-07-25T15:50:42.456430712+02:00" (when turning time.Time to JSON with json.Marshal()). Currently there is no other solution than using a separate parsing library to get correct dates.
https://www.php.net/manual/en/datetime.formats.php
今回の件がまさに json.Marshal()
経由でデータをもらっており、そのとおりなのである。
そもそもGoの仕様ってどうなってるの?
基本は自分でFormat()を呼んで指定したフォーマットで文字列化するんですが、fmt.Sprintfなどで文字列化されるときはString()の結果ですね https://t.co/gfQxwGx4Cr
— fujiwara (@fujiwara) 2019年12月24日
秒以下はnanoまで持ってるんですが、文字列になる時に末尾の0は消えるので、microに見えるのは下3桁が000なのではないかと
RFC3339Nanoなので、最大ナノセカンドまで(小数点以下9桁)の精度で値が返されますが、OS等の環境のクロックの精度にもよるし、末尾の0は切り捨てられるので、小数点以下の桁数は1~9桁になります。
— songmu (@songmu) 2019年12月24日
何桁まで返してくるかはOS依存
❯❯❯ date --iso-8601=ns
— そーだい@初代ALF (@soudai1025) 2019年12月24日
2019-12-24T18:01:02,301711400+09:00
❯❯❯ date --iso-8601=ns
2019-12-24T18:01:02,842222600+09:00
❯❯❯ date --iso-8601=ns
2019-12-24T18:01:03,264877700+09:00
WSLの実行結果です。
なのでWSLで実行してる時は末尾2桁をGoが切り捨てるのでたまたま動く
まとめ
- PHPで時間を使う時はmicrosecまで
- 言語の仕様はしっかりと理解する
- ローカルの開発環境の振る舞いをしっかりと理解する