JavaScript の new Date で日付文字列パースしないほうがいい
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ 2020-01-10 は UTC
◆ 2020/01/10 はロケールのタイムゾーン
◆ そもそも new Date(string) や Date.parse(string) はブラウザ依存で使うべきじゃなかった
◆ 2020/01/10 はロケールのタイムゾーン
◆ そもそも new Date(string) や Date.parse(string) はブラウザ依存で使うべきじゃなかった
new Date("2020-01-10 01:00:00")
// Fri Jan 10 2020 01:00:00 GMT+0900 (日本標準時)
という感じで普通にパースできていたのでこれまで気にせず new Date で日付文字列をパースしていました
ですが 分かりづらい罠がありました
日付のみの文字列の場合はその日の 0 時となることを期待します
new Date("2020/01/10")
// Fri Jan 10 2020 00:00:00 GMT+0900 (日本標準時)
想定どおりです
ですがこれが 「-」 区切りになったら
new Date("2020-01-10")
// Fri Jan 10 2020 09:00:00 GMT+0900 (日本標準時)
9 時になっています
UTC として解釈されて JST なら +0900 だから 9 時と補完されてしまっています
「-」区切りだと ISOString の「2020-01-10T00:00:00.000Z」を省略したものとして扱われるみたいですね
これを知らずに「-」区切りの日付文字列をパースして使っていたので UTC 扱いの日時での検索になっていました
それでなんか結果の件数がおかしい気がすると思ってちゃんと調べてみたらこういう理由でした
ブラウザでクエリを作ってサーバに送る過程で Unix Timestamp だとパット見でわかりづらいし JavaScript だと単位が ms だけど一般的な s にすべきかとか迷うことがあるからいっそ文字列にしようとしたのですが文字列化してのパースはこういう問題があるので数値型として扱ったほうが良かったです
文字列にするなら「-」を「/」に置換するか「 00:00:00」を末尾につけることで対処できますけど なんかスッキリしないし忘れた頃になんで無駄なことしてるんだろうとか思って日付だけにして再発しそうな感じもします
他の言語の場合
他の言語の場合もこういうことがあるのか調べてみましたPython
dateutil.parser.parse でパースしますPython の datetime 型はタイムゾーンを持ってるのと持ってないのがあって "2020-01-10" をパースしてもタイムゾーンを持たないデータとなります
そのためずれることなく 0 時となってました
ISOString の形式で UTC であることを明示するとタイムゾーン情報を持つようになりますが 時刻も指定してるので困ることはありません
C#
DateTime.Parse でパースしますDateTime 型には Kind プロパティがあって "2020-01-10" をパースした場合は Unspecified になってタイムゾーン情報は持っていません
そのため時刻は 0 時となってずれはなしです
PHP
date_parse でパースします特別な型はなくただの連想配列で結果を受け取ります
時刻部分がない場合は 0 が補完されず hour などには false が入っています
日付だけで時刻情報はないのでずれることはないです
DateTime 型を使う方法もあります
こっちはコンストラクタの第 2 引数でタイムゾーンを指定します
指定しない場合はデフォルトで php.ini などで設定したタイムゾーンとなります
Asia/Tokyo を指定した状態で試してみるとタイムゾーンは持ちますが「-」区切りでも UTC 解釈されたりはせず 0 時となりずれはありません
Ruby
DateTime.parse でパースしますDateTime.now ではタイムゾーン設定が反映されて日本のタイムゾーンになりますが parse 時は指定がなければ区切り文字関係なく UTC でした
Time.parse でもパースでき こっちの場合は指定がなければタイムゾーンの設定が使われます
まとめ
他の言語をいくつか見てみましたが JavaScript のように変な補完をする言語はないようです「-」区切りでも「/」区切りでもタイムゾーンの指定がなければタイムゾーンなしとして扱われたり システムの設定が使われます
JavaScript はなんでこんな動きなんでしょう?
他言語でもこういうのだと納得できたのですがそうでもなかったです
ブラウザの歴史的な理由とかありそうですが ES2015 あたりで統一されなかったのでしょうか
一応 Chrome だけじゃなく Firefox でも確認して同じ動きなので仕様っぽさはありますけど
でも Chrome と Firefox でパース結果が違うこともあった気がするようなしないような……
探してみたら以前 こんな記事書いてました
MDN
とりあえず MDN でも見てみましょうhttps://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date
注: ブラウザごとに動作が異なり一貫性がないため、Date コンストラクタ (または同等の Date.parse) で日付文字列を解釈しないように強くすすめます。RFC 2822 形式の文字列のサポートは、慣例にすぎません。 ISO 8601 フォーマットのサポートは、日付のみの文字列 (例: "1970-01-01") が地方時ではなくUTCとして扱われる点で異なります。
明らかに非推奨ですね
(英語版だと deprecated ではなく strongly discouraged)
new Date に文字列いれるのはやめましょう