はじめに
GoでAPIの日時フォーマットを決めるとき、ISO 8601とRFC3339のどちらを使えばよいか迷うことがあります。
実際にレビューでも「ISO 8601と書いているが、どの表記を想定しているのか分からない」という指摘を受けたことがあります。ISO 8601は範囲が広いため、仕様書に「ISO 8601形式で」と書くだけでは実装側の解釈がずれやすいのです。
結論から言うと、GoではRFC3339を基準にするのが無難です。
この記事では、両者の違いとGoでの使い分けを整理します。
ISO 8601とRFC3339の関係
まず前提として、RFC3339はISO 8601を完全に置き換えるものではありません。
- ISO 8601はより広い規格で、日時表現のバリエーションが多いです
- RFC3339はISO 8601をベースにしつつ、インターネットでの日時表現向けに絞った仕様です
ISO 8601は「年月日と時刻を一定のルールで表す」という大きな枠組みを定めていますが、表現のバリエーションが多く、どの形式を使うかは実装者に委ねられている部分があります。たとえば 2026-03-20T10:00:00Z も 20260320T100000Z もISO 8601として有効です。
RFC3339はその中から「インターネット上のプロトコルで使う」用途に絞り込んだもので、表現のバリエーションが大きく制限され、実装間で解釈が揃いやすくなっています。実装しやすく、パーサーも書きやすいのが特徴です。
「ISO 8601に対応している」と言うだけでは実装側の解釈にブレが出やすく、仕様書やレビューで認識がずれる原因になります。
GoがRFC3339を推奨している理由
Goの time パッケージは time.RFC3339 と time.RFC3339Nano を標準で持っており、APIやJSONで日時を扱うときの事実上の標準フォーマットとして広く使われています。
t := time.Now().UTC()
s := t.Format(time.RFC3339)
// 例: "2026-03-20T10:00:00Z"
s := t.Format(time.RFC3339Nano)
// 例: "2026-03-20T10:00:00.123456789Z"
フォーマットとパースどちらも標準ライブラリだけで完結します。
使い分けの目安
| 場面 | 使うべきフォーマット |
|---|---|
| APIレスポンス・JSON | RFC3339 |
| 外部サービス連携 | RFC3339 |
社内システムで YYYY-MM-DD などを明示したい |
独自フォーマット |
| 「ISO 8601対応」と広くうたいたい場合 | 実装はRFC3339で統一する |
「ISO 8601に準拠する」と言いながら実装でRFC3339を使うのはよくある話で、実用上は問題ありません。RFC3339はISO 8601の一部を制限した仕様であり、一般的な用途ではISO 8601の範囲内の表現として扱われます。
GoでISO 8601を直接使う必要性はあるか
結論としては、ほぼありません。
GoでISO 8601を意識する場面は、主に次のケースです。
- 外部仕様書や要件定義に「ISO 8601形式で」と明記されている
- 日付のみ(
2026-03-20)や週表記(2026-W12)など、RFC3339が扱わない表現が必要 - 複数の日時フォーマットを受け付ける入力を解析する
日付のみを扱う場合は time.DateOnly (Go 1.20以降)が使えます。
s := t.Format(time.DateOnly)
// 例: "2026-03-20"
タイムゾーン付きの日時を扱う通常のAPI開発であれば、ISO 8601を直接意識せずRFC3339で統一して問題ありません。
SwaggerのOpenAPI仕様での書き方
OpenAPI(Swagger)では日時フィールドの format にいくつかの選択肢があります。
| format | 意味 | 例 |
|---|---|---|
date-time |
RFC3339形式の日時 | 2026-03-20T10:00:00Z |
date |
日付のみ(ISO 8601) | 2026-03-20 |
公開APIの日時フィールドは date-time を使うのが標準です。
createdAt:
type: string
format: date-time
example: "2026-03-20T10:00:00Z"
format: date-time はRFC3339を意味します。OpenAPIの仕様でそう定義されているため、「ISO 8601」と書くより date-time と書く方が読み手に意図が正確に伝わります。
日付のみのフィールドは date を使います。
birthDate:
type: string
format: date
example: "2026-03-20"
タイムゾーンの扱いもSwaggerに明記しておくと、フロントとバックの認識ズレを防げます。
createdAt:
type: string
format: date-time
description: UTC時刻をRFC3339形式で返します
example: "2026-03-20T10:00:00Z"
注意点
RFC3339NanoはナノSecondの末尾ゼロを省略する
time.RFC3339Nano は末尾のゼロを省略するため、文字列の長さが変動します。
// 末尾ゼロが省略される
"2026-03-20T10:00:00.1Z" // 100ms
"2026-03-20T10:00:00.123Z" // 123ms
"2026-03-20T10:00:00.123456Z" // 123456µs
文字列として保存して辞書順ソートしたいときは、桁数が揃わないため時系列とずれることがあります。ソート要件がある場合は注意が必要です。
Goのパーサーは厳密ではない部分がある
time.Parse(time.RFC3339, s) はRFCの仕様どおりに厳密ではない部分があります。たとえばRFC上は許容されているうるう秒(秒が 60 の表現)を正しく扱えなかったり、逆にRFC上は拒否すべき表現を通してしまうケースがあります。
外部からの入力を受け付ける場合は、パース後の値を検証する処理を別途入れることを検討します。
まとめ
GoでAPIの日時フォーマットを決めるときは、RFC3339を基準にするとブレにくくなります。
- ISO 8601は広い規格で解釈にブレが出やすい
- RFC3339はISO 8601の部分集合で、GoやAPIに適したフォーマット
-
time.RFC3339/time.RFC3339Nanoが標準で使えるため、外部ライブラリ不要 -
RFC3339Nanoを保存・ソートに使う場合は末尾ゼロ省略に注意する - GoでISO 8601を直接意識する必要はほぼなく、RFC3339で統一すれば足りる
- SwaggerではタイムスタンプフィールドにFormat:
date-timeを使うとRFC3339が明示できる