🤖

GoでISO8601とRFC3339のどちらを使うべきか

に公開

はじめに

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:00Z20260320T100000Z もISO 8601として有効です。

RFC3339はその中から「インターネット上のプロトコルで使う」用途に絞り込んだもので、表現のバリエーションが大きく制限され、実装間で解釈が揃いやすくなっています。実装しやすく、パーサーも書きやすいのが特徴です。

「ISO 8601に対応している」と言うだけでは実装側の解釈にブレが出やすく、仕様書やレビューで認識がずれる原因になります。

GoがRFC3339を推奨している理由

Goの time パッケージは time.RFC3339time.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が明示できる

Discussion