(この記事は 地平線に行く とのマルチポストです)
PHP には、日付文字列をパースするための strptime
という関数があります。
これを使って、Sun, 19 Apr 2015 11:43:30 GMT
という文字列を %a, %d %b %Y %H:%M:%S %Z
というフォーマットでパースした結果、以下の通り環境によって異なる結果になりました。
strptime
は環境によって挙動が違うんですね。
OS | tm_year | tm_mon | tm_mday | tm_hour | tm_min | tm_sec | unparsed |
---|---|---|---|---|---|---|---|
Linux | 115 | 3 | 19 | 11 | 43 | 30 | "GMT" |
Mac | 115 | 3 | 19 | 20 | 43 | 30 | "" |
Linux の場合は GMT
で返ってきましたが、Mac の場合は JST
で返ってきました。
(どちらも、OS のタイムゾーン設定は JST
です)
どうしてこのような違いが起きたのか、ドキュメントを確認してみました。
PHP のドキュメント
ちゃんとOSにより挙動が異なることが明記されています。
注意:
内部では、 この関数はシステムの C ライブラリ関数 strptime() をコールしています。 このライブラリ関数は、OS によって挙動が異なることがあります。
PHP: strptime - Manual
ただ、どのように挙動が異なるかは記載されていません。そのため、次に各OSのドキュメントを確認してみました。
Linux の場合
フォーマットの指定として %Z (タイムゾーン) を指定することはできるが、無視すると書かれています。
原文:
For reasons of symmetry, glibc tries to support for strptime() the same format characters as for strftime(3). (In most cases the corresponding fields are parsed, but no field in tm is changed.) This leads to
%Z The timezone name.日本語:
対象性のために、glibc では strptime() に strftime(3) と同じフォーマット文字をサポートさせようとしている。多くの場合、**対応するフィールドが解釈されるが、tm フィールドは変更されない。**使用可能なフォーマット文字を以下に示す。
%Z タイムゾーン名
そのため、文字列中のタイムゾーンの指定が GMT
だろうが JST
だろうが、以下のように同じ結果が返ってきます。「11時って書いてあるんだから11時だろ」という解釈です。
入力 | tm_year | tm_mon | tm_mday | tm_hour | tm_min | tm_sec | unparsed |
---|---|---|---|---|---|---|---|
Sun, 19 Apr 2015 11:43:30 GMT |
115 | 3 | 19 | 11 | 43 | 30 | "GMT" |
Sun, 19 Apr 2015 11:43:30 JST |
115 | 3 | 19 | 11 | 43 | 30 | "JST" |
Mac (BSD系UNIX) の場合
現地のタイムゾーンに変換してくれるそうです。
原文:
The strptime() function parses the string in the buffer buf, according to the string pointed to by format, and fills in the elements of the struc-ture structure ture pointed to by tm. The resulting values will be relative to the local time zone.日本語:
strptime() 関数は、formatが指す文字列に従ってバッファbuf内の文字列を解析し、timeptr が指す構造体の要素にあてはめます。 結果の値は現地のタイムゾーンを基準にしたものになります。
ただし、文字列中のタイムゾーンとして指定できるのは GMT
かローカルタイムゾーン (今回の場合は JST
) のみだそうです。
原文:
The %Z format specifier only accepts time zone abbreviations of the local time zone, or the value "GMT". This limitation is because of ambiguity due to of the over loading of time zone abbreviations. One such example is EST which is both Eastern Standard Time and Eastern Australia Summer Time.日本語:
%Z フォーマット指定子は、ローカルタイムゾーンのタイムゾーンの省略形、または値 "GMT"のみを受け入れます。 この制限は、タイムゾーンの略語のオーバーロードによるあいまいさのためです。 そのような例の1つは、東部標準時と東オーストラリアの夏時間の両方であるESTです。
やってみると、こうなりました。「GMT
の11時って書いてあるんだから、現地時間の20時だろ」という解釈です。
入力 | tm_year | tm_mon | tm_mday | tm_hour | tm_min | tm_sec | unparsed |
---|---|---|---|---|---|---|---|
Sun, 19 Apr 2015 11:43:30 GMT |
115 | 3 | 19 | 20 | 43 | 30 | "" |
Sun, 19 Apr 2015 11:43:30 JST |
115 | 3 | 19 | 20 | 43 | 30 | "" |
OSによらず、同じ結果になるようにしたい場合は?
マニュアルに書いてありました。date_parse_from_format 関数
を使いましょう。
date_parse_from_format() はこの問題の影響を受けないので、PHP 5.3.0 以降ではこちらの関数を使うことを推奨します。
PHP: strptime - Manual
まとめ
PHP は Write once, Run anywhere じゃなかった。