CSVをインポートしようとしたら「日付形式が不正です」と弾かれた——ファイルを見ると2026/4/21・2026-04-21・21/4/2026が 混在していた。そんな経験はないでしょうか。日時フォーマットは地域・言語・システムによってバラバラで、 整えずに扱うと高確率でバグの温床になります。この記事では、国際標準のISO 8601から各言語の書式指定子、 タイムゾーン・ロケールでのハマりどころまで、横断的に整理します。
ISO 8601 — 世界標準の日時表記
ISO 8601(International Organization for Standardization 8601)は、日付と時刻を国際標準として 定めた規格です。基本形式は「YYYY-MM-DDTHH:mm:ss±HH:mm」で、 日付と時刻を「T」で区切り、末尾にタイムゾーンオフセットを付けます。
# ISO 8601 の代表的な表記例
2026-04-21 # 日付のみ
2026-04-21T12:34:56Z # UTC(Zが付く)
2026-04-21T21:34:56+09:00 # JST(+09:00 オフセット)
2026-04-21T12:34:56.789Z # ミリ秒付きUTC
ISO 8601の最大の利点は文字列として辞書順にソートしても時系列順になることです。2026-04-21と2026-04-22は 文字列比較でも前者の方が「小さい」と判定されます。 一方「21/04/2026」のような形式では、文字列ソートすると月や年で順序が崩れるためログやDBで扱いにくくなります。
strftime系のフォーマット指定子早見表
C言語のstrftime関数に由来する書式指定子は、Python・PHP・Ruby・Goの一部など多くの言語で使われています。 主要な指定子を押さえておくと言語を横断して日時処理ができます。
| 指定子 | 意味 | 例(2026-04-21 09:05:07 JST) |
|---|---|---|
| %Y | 4桁の年 | 2026 |
| %y | 2桁の年(下2桁) | 26 |
| %m | 月(2桁ゼロ埋め) | 04 |
| %d | 日(2桁ゼロ埋め) | 21 |
| %H | 時(24時間制・2桁) | 09 |
| %I | 時(12時間制・2桁) | 09 |
| %M | 分(2桁ゼロ埋め) | 05 |
| %S | 秒(2桁ゼロ埋め) | 07 |
| %A | 曜日名(ロケール依存) | Tuesday / 火曜日 |
| %a | 曜日名の略 | Tue / 火 |
| %Z | タイムゾーン名 | JST |
| %z | タイムゾーンオフセット | +0900 |
なお、Pythonの「%f」はマイクロ秒(6桁)、PHPの「u」もマイクロ秒です。 ミリ秒を3桁で欲しい場合はアプリ側での加工が必要になります。 各言語の個別フォーマットは日時フォーマット横断検索ツールで一覧比較できます。
言語・プラットフォーム別のフォーマット指定
「同じ年月日を出したいだけなのに書式指定子が違う」という現象は日常茶飯事です。 代表的な環境での指定方法を比較表にまとめます。
| 環境 | 「2026-04-21 12:34:56」を出す書式 | 備考 |
|---|---|---|
| JavaScript(toISOString) | date.toISOString() | 常にUTC・ミリ秒付き |
| JavaScript(Intl) | Intl.DateTimeFormat('ja-JP') | ロケール依存・柔軟 |
| Python | strftime('%Y-%m-%d %H:%M:%S') | strftime系 |
| Go | Format("2006-01-02 15:04:05") | Reference Time方式(独自) |
| Java(DateTimeFormatter) | "yyyy-MM-dd HH:mm:ss" | 「MM」が月・「mm」は分 |
| C#(.NET) | "yyyy-MM-dd HH:mm:ss" | Javaとほぼ同じ |
| Excel(セル書式) | yyyy/mm/dd hh:mm:ss | 全て小文字・独自仕様 |
JavaScript
// ISO 8601(UTC・最もシンプル)
new Date().toISOString() // "2026-04-21T12:34:56.789Z"
// ロケール指定で日本語フォーマット
new Intl.DateTimeFormat('ja-JP', {
year: 'numeric', month: '2-digit', day: '2-digit'
}).format(new Date()) // "2026/04/21"
Python
# strftime で書式指定
from datetime import datetime
datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# ISO 8601 形式(ミリ秒付き)
datetime.now().isoformat() # "2026-04-21T12:34:56.789123"
Go — Reference Timeという独自方式
Goは他の言語と大きく異なり、「2006-01-02 15:04:05」という特定の日時をリファレンスとして書式を指定します。覚え方は「1・2・3・4・5・6」の順に「1月・2日・15時(=3PM)・4分・5秒・2006年」と並んでいることです。
// Go - Reference Timeを使ったフォーマット
t.Format("2006-01-02 15:04:05") // "2026-04-21 12:34:56"
ロケール依存フォーマットの罠
同じ「date」コマンドや言語標準のフォーマッタでも、OSのロケール設定によって出力が変わる場合があります。これが原因で本番環境と開発環境で挙動が違うという事故が起きやすいです。
| ロケール | 短縮日付(例) | 読み方 |
|---|---|---|
| en-US(アメリカ) | 4/21/2026 | 月/日/年 |
| en-GB(イギリス) | 21/04/2026 | 日/月/年 |
| ja-JP(日本) | 2026/04/21 | 年/月/日 |
| de-DE(ドイツ) | 21.04.2026 | 日.月.年 |
特に「4/21/2026」と「21/4/2026」は、月と日が共に12以下の日付では区別がつかないため深刻です。ログやデータ交換では必ずISO 8601を使い、ロケール依存フォーマットは表示直前だけに留めるのが鉄則です。
タイムゾーンの落とし穴
日時を扱う最大の難関がタイムゾーンです。同じ瞬間でも見る場所によって時刻が異なるため、 「いつ・どのゾーンで保存し、いつ・どこで変換するか」を明確に決めないと容易にバグります。
UTC・JST・ローカルタイム
UTC(Coordinated Universal Time、協定世界時)は世界共通の基準時刻で、タイムゾーンを持ちません。 JST(Japan Standard Time)はUTC+9時間で、日本国内でのみ使われます。 「ローカルタイム」はプログラムが動作している環境のOS設定に従った時刻で、 サーバーの設定次第でUTCだったりJSTだったりします。
よくあるバグ
ローカル環境(JST)では「今日」として保存したのに、本番サーバー(UTC)では「昨日」になっていた—— これはDateオブジェクトを作る際にタイムゾーンを指定しなかったことが原因です。 保存時は必ずUTCに正規化し、表示時にIANA(Internet Assigned Numbers Authority)タイムゾーン名(例: Asia/Tokyo)で変換しましょう。
Date.parse の環境差
JavaScriptのDate.parse()やnew Date(string)は、 ISO 8601以外の形式では環境依存の挙動をします。たとえばnew Date('2026-04-21')は UTCの00:00として解釈されますが、new Date('2026/04/21')は ローカルタイムの00:00として解釈されます(たった1文字「-」か「/」かの違いで挙動が変わります)。
この挙動の違いは日本のブラウザで「9時間ずれて表示される」バグの主因です。 パースは常にISO形式を使うか、date-fnsやLuxon・day.jsなどのライブラリに任せるのが安全です。
2桁年問題 — Y2KとY2K38
日時フォーマットで2桁年(%y / yy)を使うのは、表示目的でない限り避けるべきです。 歴史的に2つの有名な年問題があり、いずれも2桁年表現・整数型の上限が原因でした。
| 通称 | 発生時期 | 原因 |
|---|---|---|
| 2000年問題(Y2K) | 1999→2000 | 2桁年「99」→「00」で年度計算が破綻 |
| 2038年問題(Y2K38) | 2038-01-19 03:14:07 UTC | 32bit符号付き整数のUNIXタイムスタンプがオーバーフロー |
2038年問題の詳細とオーバーフロー後の挙動についてはUNIXタイムスタンプ変換ツールのページで実際の変換を試しながら確認できます。 日時を整数で扱うときは必ず64bit整数を使うのが現代の標準です。
UNIXタイムスタンプとISO形式の使い分け
日時を機械的に扱う場面では、UNIXタイムスタンプ(秒またはミリ秒の整数)とISO 8601文字列のどちらを使うかで迷います。 それぞれの得意領域を整理しておくと選択が楽になります。
- UNIXタイムスタンプ: 計算(差分・加算)・ソート・DB保存のプリミティブとして最適
- ISO 8601: 人が目視で確認可能・APIやログでの可読性に優れる
- 実装上はDBにUTCで保存(timestamptzまたはBIGINT)し、APIレスポンスはISO 8601で返す組み合わせが定番
日付だけの計算(今から30日後など)を気軽に試したい場合は日付計算ツールが便利です。ブラウザ上で即座に結果が出るため、API設計時の動作確認に使えます。
まとめ
- 機械可読な日時表現はISO 8601(YYYY-MM-DDTHH:mm:ss±HH:mm)で統一する
- strftime系(%Y・%m・%d)とExcel書式(yyyy・mm・dd)は別物。大文字小文字を混同しない
- GoのみReference Time(2006-01-02 15:04:05)という独自方式
- ロケール依存フォーマット(4/21/2026 vs 21/4/2026)は表示直前だけに留める
- DB保存はUTC、表示時にIANAタイムゾーン名(Asia/Tokyo等)で変換するのが鉄則
new Date('2026/4/21')のようなスラッシュ区切りパースは環境依存で危険- 2桁年・32bit整数の使用はY2K・Y2K38の再現を招くため避ける
- 各言語のフォーマット指定子は日時フォーマット横断検索で横断比較できる