2026.09.23 泊まれる場所4,223 立ち寄りスポット59,693 営業中を確認したキャンプ場1,005 note を読む

JavaScriptの「Invalid Date」の意味|自分のパソコンでは出ていた日付が、iPhoneでは消えていた

投稿日を画面に出すだけの処理なのに、僕の手元とスマホで結果が違った。
JavaScriptの日付は、渡す文字列の書き方ひとつで読めなくなる。

記事の一覧に「3日前」「先週」といった表示を出そうと思って、投稿日をJavaScriptで扱う処理を書いた。
自分のパソコンでは、狙いどおりに出た。
その日の夜、スマホで同じページを開いたら、日付のところに「Invalid Date」と出ていた。
書き間違いを探して、しばらく無駄な時間を使った。

そもそも「Invalid Date」とは何なのか

「Invalid Date」は、JavaScriptのDateオブジェクトが「この文字列は日付として解釈できませんでした」と申告している状態そのものだ。
エラーが投げられて処理が止まるわけではなく、Dateオブジェクトは普通に生成されたうえで、中身が空っぽになっている。
だからconsole.log(date)には「Invalid Date」と出るのに、コード自体は最後まで実行される。
画面には日付らしき文字が表示されないだけで、エラーログには何も残らない。
このおとなしさが厄介で、僕も本番でしばらく気づかなかった。

渡していた文字列が、そもそも読めていなかった

僕が書いていたのは、こういう一行だった。
new Date('2026-08-24 10:30:00')
データベースから取り出した日時の形をそのまま渡していた。

この形は、日付と時刻のあいだが半角スペースになっている。
実はこの書き方、JavaScriptの決まりごとの中には入っていない。
読めるブラウザもあれば、読めないブラウザもある、という宙ぶらりんな状態だった。
僕のパソコンのブラウザは親切に解釈してくれていて、スマホのSafariは解釈しなかった。
それだけの違いで、片方だけ壊れて見えていた。

なぜChromeは通って、Safariは弾いたのか

ここが「意味」を知りたくて調べているときに一番引っかかるところだと思う。
ECMAScriptの仕様が正式に決めている書き方は、ISO 8601という国際規格に沿った形だけだ。
それ以外の書き方(今回のスペース区切りもそう)は、仕様の外側にあって、各ブラウザの実装まかせになっている。
ChromeやEdgeが使っているV8というエンジンは、この手の緩い書き方をわりと寛大に解釈してくれる。
SafariのJavaScriptCoreは仕様に忠実であろうとする分、決められた形から外れた文字列を素直に「読めません」と返してくる。
僕の手元と自分のスマホで結果が割れたのは、バグではなく、仕様が空けている余白をブラウザごとに違う形で埋めているだけだった。

スラッシュに変えると直る。でも直った理由が弱い

検索するとすぐ「スラッシュ区切りにすれば通る」と出てくる。
new Date('2026/08/24 10:30:00')
たしかにこれで、手元でもスマホでも表示された。

ただ、これも決まりごとで保証された書き方ではなくて、たまたま多くのブラウザが受け付けているだけだった。
今日動いているのは分かるけれど、明日も動く根拠がない。
そういう直し方は、後でまた同じ場所で転ぶ。

決まりごとに沿った形は、日付と時刻を大文字のTでつなぐものだった。
new Date('2026-08-24T10:30:00')
スペースをTに置き換えるだけで、どのブラウザも同じ読み方をしてくれる。

Tを付けたら、今度は9時間ずれた

安心したのも束の間で、末尾にZを付けた書き方を見つけて真似したら、表示が9時間ずれた。
'2026-08-24T10:30:00Z'のZは、世界標準時という意味だった。
日本時間の10時半のつもりで渡した値が、日本時間の19時半として表示されていた。

これは他人事ではなくて、僕は同じずれで一度つまずいている。
手元の開発環境のサーバーが世界標準時で動いていて、本番のサーバーが日本時間で動いていた。
予約投稿した記事が、手元では時間になっても出てこないのに、本番では出ている。
バグを疑って中身を読み返したけれど、原因はコードではなく、時計のほうだった。
そのときのサーバーの時計が日本時間で動いていなかった話は、今回のZの件とつながる同じ穴だと思う。

時刻を書かないと、こっそりUTC扱いになる

調べていて、もうひとつ知らずに踏みそうな形を見つけた。
new Date('2026-08-24')のように、時刻を書かず日付だけを渡す形。
これは決まりごとの範囲内の書き方なのに、時刻つきの形とは扱いが違って、世界標準時の午前0時として解釈される。
日本は世界標準時より9時間進んでいるので、日本時間では前日の9時として表示されることになる。
同じISOの形なのに、時刻を書くかどうかだけで「現地時間」と「世界標準時」が入れ替わる。
僕はこれを知らずに、日付だけのフィルタ処理で1日ずれた表示を出してしまったことがある。

いまは、日付の解釈をJavaScriptに任せない

あれから方針を変えて、画面に出す文字はサーバー側で作るようにした。
「2026年8月24日」と書かれた文字列を、そのまま受け取って、そのまま出す。
JavaScriptに渡すのは、計算が必要なときだけにした。

計算が要るときも、文字列ではなく数値で渡している。
1970年からの経過秒を数字で渡せば、書き方の揺れが入り込む余地がない。
HTMLのほうにものように、機械が読むための値を別に置いた。
人が読む文字と、機械が読む値を、はじめから分けて持つ。

Invalid Dateかどうかは、こう確かめる

もう一つ、地味だけど効いた変更がある。
日付を扱う処理の入口で、isNaN(date.getTime())で先に確かめるようにした。
Dateは失敗しても例外を投げてくれないので、こちらから聞きにいかないと気づけない。
ここで弾いておけば、画面に「Invalid Date」という文字がそのまま出る事故は防げる。
直す前と直した後で違うのは、日付が読めなかったときに「何も表示しない」か「壊れた文字を表示する」かという一点で、これだけでも印象はだいぶ変わる。

スプレッドシートやデータベースでも、同じ壊れ方をする

調べていて気づいたのは、これはJavaScriptだけの話ではないということだった。
Excelのセルに日付を入れると、画面には「2026/8/24」と見えていても、内部では「1900年1月1日からの経過日数」という、ただの数値で持っている。
セルの書式設定を数値に変えると、日付だったはずの表示が「46264」のような数字に化けるのはこのためだ。
僕がよく触るMySQLのDATETIME型も同じで、2026-08-24 10:30:00という文字列とタイムゾーンの区別を、そもそも持っていない。
入れた側が「これは日本時間のつもりだ」と思っていても、読み出す側が「これは世界標準時のはずだ」と解釈すれば、今回のInvalid Dateとまったく同じ種類のずれが起きる。
道具が変わっても、日付は「文字列としてどう書かれているか」と「誰の時計で読むか」がずれたときに壊れる、という構造は同じだった。

日付まわりを触るときの、5つの確認

この一件のあと、日付を扱うコードを書くときは次の順番で確認するようになった。

  • サーバーから渡す文字列は、時刻とタイムゾーンまで書いたISO 8601の形(2026-08-24T10:30:00+09:00)に統一する
  • 日付だけの文字列(2026-08-24)を渡すときは、世界標準時として解釈されることを前提に置く
  • isNaN(date.getTime())で、読み取れたかどうかを先に確かめる
  • 自分のパソコンだけでなく、スマホのSafariでも同じ画面を開いて確認する
  • 本番とローカルでサーバーの時計が揃っているかを、サーバーの時計が日本時間で動いていなかった話で書いた方法で確かめる

特別なことは何もしていない。
ただ、この5つを毎回同じ順番で確認するようにしただけで、Invalid Dateにまつわる事故は起きなくなった。

今回いちばん効いたのは、直し方そのものより、確認の仕方を変えたことだった。
自分のパソコンで表示された時点で「動いた」と判断していたのが、そもそもの入口だった。
日付まわりを触った日は、必ずスマホでも開く。
それだけで、この手の見落としはずいぶん減った。

まる子パパ

まる子パパ

会社員。受託開発のエンジニアを15年以上続けていて、その前は寿司職人・長距離運転手・農業。 技術ブログは、キャンプの合間に踏んだバグと、AI・Web開発の運用の失敗を書いています。 このサイト自体が、開発に携わっているCMS「コンテナ」の稼働中の実例です。 このサイトについて / 小さな自動化の相談

—

ほかにも書いています

受託・note・スタンプ

※受託の相談とnoteは、運営者が個人で受けている・書いているものです。

技術ブログ一覧へ戻る