発端は、d="2018-09-11 10:00:00"というような形式の文字列をjsのプログラムに渡したら、new Date(d)で一部環境(iPhone + Chrome)ではNaNが返ったこと。
それからあれこれ調べて、JavaScriptのプログラムに文字列で時刻を渡すのであれば、"2018-09-11T10:00:00+09:00"というフォーマットが無難かなぁと。JavaScriptと書いたけれど、ISO準拠なので、他のケースでも考え方は同じかと思う。
キーワード: ISO8601拡張形式, RFC3339, ECMAScript
時刻の文字列のフォーマットは何が良い?
アプリケーション側のjsでAPIを叩いて、jsonで時刻"YYYY-mm-dd HH:MM:SS"を受け取り、受け取った文字列dをnew Date(d)したところ、PCでは問題なかったが、iPhone + ChromeでgetHours()やらしてもNaNが返った。「chrome safariでnew Date()したら中身がNaNになってしまう。 | 初めてのレンタルサーバー.NET」の記事によると、iPhone + Chrome/Safariでは"YYYY-mm-dd HH:MM:SS"ではだめで、しかし"YYYY/mm/dd HH:MM:SS"では問題ないという。
自分はAPI側を実装していて、大元の時刻情報をUNIXタイムスタンプで保持していたから、アプリケーション側に渡すフォーマットはなんでもよかった。"YYYY-mm-dd HH:MM:SS"にしたのは、まぁ馴染みがあるかな、くらいで深く考えてはいなかった。けれど、こうして問題が生じたので、一度ちゃんと考えないとなぁ、と。
以下、JavaScriptに渡す時刻を表現した文字列は、どんなフォーマットがよいのか、調べてつらつらと考えてみる。
ISO8601拡張形式が無難
今回JavaScriptで受け取った文字列は、new Date(d)でDate型に変換していた。まぁだいたいそうするものと思われる。このnew Date(d)は何するものぞ、ということで、ECMAScript 2015を見る(「https://www.ecma-international.org/ecma-262/6.0/#sec-date-constructor」)。
20.3.2.2 Date( value )節を見ると、valueが文字列の時はparseメソッドを呼ぶらしい。で、20.3.3.2 Date.parse( string )節によると、20.3.1.16節で定義した Date Time String Formatに則って文字列を解釈するのだと。で、ようやく「ECMAScript 2015 Language Specification – ECMA-262 6th Edition」にたどり着くと、以下に則っているそうな。
YYYY-mm-ddTHH:MM:SS.sssZ
sssはミリ秒、Zはタイムゾーンだ(UTCはZでよく、それ以外は、オフセットを+-HH:MMで表現する)。たとえば日本時間の2018年9月11日13時32分だと、2018-09-11T13:32:00.000+09:00と表現される。これは日時のフォーマットについて定めたISO8601拡張形式に準じている。
なので、「JavaScriptに渡す時刻を表現した文字列は、どんなフォーマットがよいのか」に対する答えは、このフォーマットで良いだろう。
ちなみにけっこう色々と省略できるみたいだ。
- 日付
- YYYY
- YYYY-mm
- YYYY-mm-dd
- 時刻
- 日付との区切りは"T"
- HH:MM
- HH:MM:SS
- HH:MM:SS.sss
- タイムゾーン
- オプション(なくてもよい)
- ±HH:MM
以上を組み合わせて、日付オンリーまたは日付T時刻タイムゾーンとなっていればよい。YYYY-mm-ddとか、知らず知らずのうちにES準拠になっていたパターンはけっこうあるんじゃなかろうか(参考「Date.parseとタイムゾーン | メモログ」YYYY-mm-ddとYYYY/mm/ddで挙動が異なることについての考察記事)。
適宜省略したほうが人間には優しいが、読み取るのはコンピュータであるし、特にタイムゾーンの省略は不安なので、省略しないのが一番安全かなと思う。でもミリ秒くらいは省略してもいいかもなぁ(後述のRFC3339)……000ってつけるのケッタイだし。。。すると、結論的には以下となる。
YYYY-mm-ddTHH:MM:SSZ
なお、Date型に変換した後、普通にgetHours()とかするとLT(ローカルタイム)が適用される。UTCとLT以外で時刻を取得するには、オフセットかますとか一工夫いるそうな。それだったらUTCで取得していじったほうが早いかもしれない。
それ以外のフォーマットのとき
ESの形式から外れた時のことについて。まさに今回の発端がそうだったわけだが、標準から外れると環境依存が生じる。文字列の解釈がブラウザに任されるからだ。「Dateオブジェクト (日付と時刻) | JavaScript プログラミング解説」の記事では色々と試してくれていて、これを見るだけでもけっこうカオス。まぁ標準通りがいいんだね、と。IE8がISO8601拡張形式に対応していないようだが、さすがに2018年現在、考慮しなくて良いだろう。
ISO8601とRFC3339
この手の話を調べていると、ISO8601ではなくてRFC3339という呼ばれ方をしているものが散見される。「RFC3339 インターネット上の日付と時間:タイムスタンプ」を読むと、RFC3339は、ISO8601拡張形式をシンプルかつ明示的に再定義したものだ。
(追記)本節の元のミリ秒に関する記述について間違いをご指摘いただきましたので、訂正します。「RFC3339 インターネット上の日付と時間:タイムスタンプ」の5.6節Internet Date/Time Formatにはtime-secfracがあります。また、5.8節Exampleにはミリ秒を用いた例があります。
UNIXタイムスタンプは?
ところで、UNIXタイムスタンプはどうだろうか。今回のケースなんかは特に、元のデータがそうなので、これをそのまま返す、ということも考えられる。
恐らく、Date( timestamp) でも機能的には特に問題が生じないように思う。2038年問題に注意することくらいだろうか。しかし返り値はデバッグ時に人の目でも確認するところであるし、またミリ秒で数えるので、1000倍するのがなにやらケッタイだから、やはりISO8601拡張形式で返すほうが良いと思う。
参考
- chrome safariでnew Date()したら中身がNaNになってしまう。 | 初めてのレンタルサーバー.NET
- ECMAScript 2015 (6th Edition, ECMA-262) Date.parse の定義
- Date and Time Formats
- ECMAScript 2017 に準拠した方法で Date オブジェクトをシリアライズ(文字列化)する
- Date.parseとタイムゾーン | メモログ
- RFC3339 インターネット上の日付と時間:タイムスタンプ
- ISO 8601:2004 - Data elements and interchange formats -- Information interchange -- Representation of dates and times
- Dateオブジェクト (日付と時刻) | JavaScript プログラミング解説
コメント