API やログでよく見る 2026-10-30T09:00:00+09:00 の形(ISO 8601)を読んで、UTC と日本時間で書き直す。日時を扱うシステムなら、どこかで必ず書く処理です。各言語の標準のパーサーにそのまま渡す、いちばんよく見かける書き方で試しました。
2026-02-30T09:00:00Z(2月30日)を、9言語中4言語(JavaScript・PHP・Ruby・Swift)が、エラーにせず 3月2日 として読みました。
何をしたか
CODE Polyglot の「よく使う書き方」 で、受け付ける形と出力を先に決めました(正解表15件)。
- 形は
YYYY-MM-DDTHH:MM:SS。秒の小数とタイムゾーン(Zか±HH:MM)を付ける - タイムゾーンが無ければ NG。ありえない日付・時刻、
+0900、空白区切りも NG
よく見かける書き方は9言語で用意しました。Rust は標準に日時のパーサーが無い(よく使われるのは外部の chrono)ので、置いていません。
標準のパーサーは、思ったより何でも読む
| よく見かける書き方 | 2月30日(c010) | 日付だけ(c013) | +0900(c014) |
|---|---|---|---|
JavaScript new Date(s) | 3月2日 | 読む(UTC として) | 読む |
PHP new DateTimeImmutable(s) | 3月2日 | 読む | 読む |
Ruby Time.parse(s) | 3月2日 | 読む | 読む |
Swift ISO8601DateFormatter | 3月2日 | NG | 読む |
Python datetime.fromisoformat | NG | 読む | 読む |
C# DateTimeOffset.TryParse | NG | 読む | 読む |
Go time.Parse(RFC3339)・Java ZonedDateTime.parse・Kotlin Instant.parse | NG | NG | NG |
ゆるいパーサーは、入力の間違いを黙って直します。予約システムで「2月30日」と送ってきた相手に、3月2日の予約が入ってしまう種類の不具合です。
タイムゾーンの無い日時は、動かす場所の時間帯で読まれる
2026-10-30T09:00:00(c009)は、タイムゾーンが書かれていません。決まりでは NG ですが、9言語すべてが決まりどおりの答えを出せませんでした。中身は2通りに分かれます。
- JavaScript・Ruby・C#・Python・PHP … 何かの時間帯で読んで、日時を返した。PHP は php.ini の設定(既定は UTC)、ほかの4言語は動かす場所の時間帯
- Go・Java・Kotlin・Swift … 読めないので NG にした。ただし「タイムゾーンが無い」とは言えず、「形が違う」と答えた
前者の方が困ります。日本時間の Mac では 09:00 を日本時間として読みますが、UTC で動くサーバーでは UTC の 09:00 として読みます。同じコードの答えが9時間ずれます。
JavaScript の getHours() などは、動かす場所の時間帯の値を返します。毎朝の検証のコンテナを TZ=UTC にして回すと、JavaScript の書き方は日本時間の行がすべて UTC のまま出て、15件中1件しか合いませんでした(日本時間の環境では15件中9件)。
どう書いたか
形を先に正規表現で確かめ、ありえない日付は日時の型を作るときに弾き、時差は「時差だけを持つタイムゾーン」で扱いました。Python ならこうです(抜粋)。
ISO = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})?")
# datetime は 2月30日や25時を ValueError にする
local = datetime(int(y), int(mo), int(d), int(h), int(mi), int(se))
# timezone(timedelta) は時差だけのタイムゾーン。tzdata を見ないので、どこで動かしても同じになる
t = local.replace(tzinfo=timezone(offset))
jst = t.astimezone(timezone(timedelta(hours=9)))
10言語のコードは、日本時間の環境でも TZ=UTC の環境でも、15件すべてで決まりどおりの答えを出しています。
言語ごとの細かい違いも見つかりました。
- C# の
DateTimeOffset.Parseは、秒の小数を7桁に四捨五入する(09:00:59.999999999Zが09:01:00になった) - Go の
time.Parseは、+24:00のようなありえない時差も通す - JavaScript の
Date.UTCは、0〜99年を1900年代として扱う
まとめ
- 標準のパーサーにそのまま渡すと、2月30日を3月2日にする言語がある(9言語中4言語)
- タイムゾーンの無い日時は、動かす場所の時間帯で読まれる。Mac とサーバーで答えが変わる
- 受け付ける形を決めて先に確かめ、日本時間は「UTC に9時間を足す」形で作る
10言語のコードと全15件の結果は CODE Polyglot「日付と時刻を ISO 8601 で読み書きする」 にあります。
- 第1回:HTML のエスケープ、標準の関数の答えが言語ごとに違った
- 第2回:「2026-02-30T09:00:00Z」を、4言語の標準のパーサーが3月2日として読んだ(この記事)
- 第3回:「getHTTPResponseCode」をスネークケースに、よく見かける書き方は10言語中9言語で崩れた
- 第4回:アクセスログを空白で区切って集計したら、10言語すべてが壊れた行を数えた
- 第5回:重複を消したら順番が変わった、Swift は「が」を1つにまとめた
- 第6回:İstanbul の slug が5通りに割れた、日本語のタイトルはそのまま URL に入った
Analyzegear


