0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

0
Posted at

はじめに

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が明示できる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?