RFC 3339 - Date and Time on the Internet: Timestamps
- ステータス: Proposed Standard
- 発行日: July 2002
- ストリーム: IETF
- エラッタ: エラッタなし
Status of this Memo (このメモの状態)
この文書は, インターネットコミュニティのためのインターネット標準追跡プロトコルを指定し, 議論と改善提案を求めます。このプロトコルの標準化状態については, 現在のバージョンの「インターネット公式プロトコル標準」 (STD 1) を参照してください。このメモの配布は制限されていません。
Copyright Notice (著作権表示)
Copyright (C) The Internet Society (2002). All Rights Reserved.
Abstract (要約)
この文書は, インターネットプロトコルで使用する日付と時刻の形式を定義します。これは, グレゴリオ暦 (Gregorian Calendar) を使用して日付と時刻を表現する ISO 8601 標準のプロファイル (Profile) です。
目录 (目次)
- 1. Introduction (序論)
- 2. Definitions (定義)
- 3. Two Digit Years (2桁の年)
- 4. Local Time (現地時間)
- 4.1 Coordinated Universal Time (UTC)
- 4.2 Local Offsets
- 4.3 Unknown Local Offset Convention
- 4.4 Unqualified Local Time
- 5. Date and Time format (日付と時刻の形式)
- 5.1 Ordering
- 5.2 Human Readability
- 5.3 Rarely Used Options
- 5.4 Redundant Information
- 5.5 Simplicity
- 5.6 Internet Date/Time Format
- 5.7 Restrictions
- 5.8 Examples
- 6. References (参考文献)
- 7. Security Considerations (セキュリティの考慮事項)
附录 (付録)
- Appendix A. ISO 8601 Collected ABNF
- Appendix B. Day of the Week
- Appendix C. Leap Years
- Appendix D. Leap Seconds
相关资源 (関連リソース)
- 公式原文: RFC 3339 (TXT)
- 公式ページ: RFC 3339 DataTracker
- 正誤表: RFC Editor Errata
快速参考 (クイックリファレンス)
標準形式
YYYY-MM-DDTHH:MM:SS.sssZ
YYYY-MM-DDTHH:MM:SS.sss±HH:MM
例
1985-04-12T23:20:50.52Z
1996-12-19T16:39:57-08:00
1990-12-31T23:59:60Z (閏秒)
1937-01-01T12:00:27.87+00:20
重要な注意: これはインターネットプロトコルでタイムスタンプを表現する標準形式であり, HTTP, JSON, XML などのプロトコルおよびデータ形式で広く使用されています。
1. Introduction (序論)
日付と時刻の形式は、インターネット上で多くの混乱と相互運用性の問題を引き起こしています。本文書は、遭遇する多くの問題に対処し、インターネットプロトコルで日付と時刻を表現および使用する際の一貫性と相互運用性を改善するための推奨事項を示します。
本文書には、グレゴリオ暦 (Gregorian Calendar) を使用した日付と時刻の表現に関する ISO 8601 [ISO8601] 標準のインターネットプロファイル (Internet Profile) が含まれています。
インターネットプロトコルで日付と時刻の値が表示される方法は多数あります。本文書は、インターネットプロトコルイベントのタイムスタンプ (Timestamps) という1つの一般的な使用法のみに焦点を当てています。この限定的な考慮には、次の結果があります:
タイムスタンプの制限と仮定
o 現代
すべての日付と時刻は、「現代」、つまり西暦0000年から西暦9999年の間のどこかにあると想定されます。
o UTCとの関係
表現されるすべての時刻は、協定世界時 (Coordinated Universal Time, UTC) との明示的な関係(オフセット)を持っています。(これは、ローカル時刻と場所は既知である可能性があるが、UTCとの実際の関係が政治家または管理者の未知または不可知の行動に依存する可能性があるスケジューリングアプリケーションでの一部の使用法とは異なります。ニューヨークでの2005年3月23日17:00に対応するUTC時刻は、夏時間に関する管理上の決定に依存する可能性があります。本仕様は、そのような考慮事項を明確に避けています。)
o 歴史的タイムスタンプ
タイムスタンプは、UTCの導入前に発生した時刻を表す場合があります。そのようなタイムスタンプは、記載された時点で利用可能な最良の実践を使用して、世界時 (Universal Time) に対して相対的に表されます。
o 時点の表現
日付と時刻の表現は、時間内の瞬間 (Instant in Time) を表します。期間 (Time Periods) または間隔 (Intervals) の記述は、ここではカバーされていません。
重要なポイント:
- 本仕様はタイムスタンプに焦点を当てており、期間やスケジューリングは対象外です
- すべての時刻はUTCとの定義された関係を持つ必要があります
- 西暦0000年から西暦9999年の日付範囲をサポートします
- ローカルタイムゾーンに関する政治的決定(夏時間調整など)への依存を避けます
2. Definitions (定義)
本文書のキーワード "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"MAY"、"OPTIONAL" は、RFC 2119 [RFC2119] で説明されているように解釈されるものとします。
用語定義
UTC (Coordinated Universal Time, 協定世界時)
国際度量衡局 (Bureau International des Poids et Mesures, BIPM) によって維持されている協定世界時。
second (秒)
国際単位系 (International System of Units) における時間測定の基本単位。外部場による乱れのない基底状態のセシウム133 (cesium-133) 原子の超微細遷移によって吸収または放出されるマイクロ波光の9,192,631,770サイクルの継続時間として定義されます。
minute (分)
60秒の時間。ただし、閏秒 (Leap Seconds) が分内でどのように表されるかについては、第5.7節および附属書Dも参照してください。
hour (時)
60分の時間。
day (日)
24時間の時間。
leap year (閏年)
グレゴリオ暦において、366日ある年。閏年は、その数が4で割り切れるが100では割り切れない年、または400でも割り切れる年です。
閏年判定規則:
if (年 % 400 == 0) → 閏年
else if (年 % 100 == 0) → 平年
else if (年 % 4 == 0) → 閏年
else → 平年
例:
- 2000年: 閏年 (400で割り切れる)
- 1900年: 平年 (100で割り切れるが400では割り切れない)
- 2004年: 閏年 (4で割り切れる)
- 2001年: 平年
ABNF (Augmented Backus-Naur Form, 拡張バッカス・ナウア記法)
[ABNF] で定義されているように、プロトコルまたは言語で許可される文字列を表すための形式。
Email Date/Time Format (電子メール日付時刻形式)
RFC 2822 [IMAIL-UPDATE] で定義されているインターネットメールで使用される日付時刻形式。
Internet Date/Time Format (インターネット日付時刻形式)
本文書の第5節で定義されている日付形式。
Timestamp (タイムスタンプ)
本文書では、この用語は時間の特定の瞬間の明確な表現 (Unambiguous Representation) を指すために使用されます。
Z
時刻に適用される場合、UTCオフセットが00:00であることを示す接尾辞。ICAO音声アルファベットの文字「Z」の表現から「ズールー (Zulu)」と発音されることが多い。
例:
2002-07-15T10:30:00Z
表現: 2002年7月15日 10:30:00 UTC
詳細情報
時間スケールに関する詳細については、以下を参照してください:
- [NTP] の附属書E
- [ISO8601] の第3節
- 該当するITU文書 [ITU-R-TF]
注意: これらの基本用語を理解することは、本仕様を正しく実装および使用するために重要です。特に、閏年と閏秒の定義が重要です。
3. Two Digit Years (2桁の年)
以下の要件は、2桁の年が引き起こす曖昧性の問題に対処します:
要件
o 4桁の年は必須
インターネットプロトコルは、日付において4桁の年を生成しなければなりません (MUST)。
正しい例:
✅ 2002-07-15
✅ 1999-12-31
誤った例:
❌ 02-07-15
❌ 99-12-31
o 2桁の年は非推奨
2桁の年の使用は非推奨 (Deprecated) です。2桁の年を受信した場合、誤解釈がプロトコルまたは処理の失敗を引き起こさない場合(例えば、ログ記録または追跡目的のみに使用される場合)にのみ受け入れるべきです (SHOULD)。
o 3桁の年の処理
2桁の年を使用するプログラムは、1999年以降の年を3桁として表す可能性があります。これは、プログラムが桁数をチェックせずに単純に年から1900を減算する場合に発生します。このような欠陥のあるソフトウェアによって生成された日付を堅牢に処理したいプログラムは、3桁の年に1900を加算してもよい (MAY) です。
例:
欠陥のあるプログラム出力: 102 (2002年を表す)
堅牢な解析: 102 + 1900 = 2002
o 非数字の10年桁の処理
2桁の年を使用するプログラムは、1999年以降の年を ":0"、":1"、... ":9"、";0"、... として表す可能性があります。これは、プログラムが単純に年から1900を減算し、10年桁をUS-ASCII文字ゼロに加算する場合に発生します。このような欠陥のあるソフトウェアによって生成された日付を堅牢に処理したいプログラムは、非数字の10年桁を検出し、適切に解釈すべきです (SHOULD)。
例:
欠陥のあるプログラム出力:
- '0' + 10 = ':' (2000年代を表す、ASCIIコード58)
- '0' + 11 = ';' (2010年代を表す、ASCIIコード59)
堅牢な解析はこれらのパターンを認識し、正しく変換する必要があります
Y2K問題からの教訓
2桁の年の問題は、インターネットプロトコルで使用されるすべての日付と時刻が完全修飾 (Fully Qualified) されなければならない (MUST) 理由を十分に示しています。
実装の推奨事項
日付を生成する場合
✅ 常に4桁の年を使用: 2024-12-21
❌ 決して2桁を使用しない: 24-12-21
日付を解析する場合
# 堅牢な解析の例
def parse_year(year_str):
if len(year_str) == 4:
return int(year_str) # 正しい4桁
elif len(year_str) == 2:
# 非推奨、後方互換性のみ
year = int(year_str)
if year < 70:
return 2000 + year
else:
return 1900 + year
elif len(year_str) == 3:
# 欠陥のあるソフトウェアの処理
return 1900 + int(year_str)
else:
raise ValueError("Invalid year format")
警告: このセクションでは2桁の年の処理方法を説明していますが、これは後方互換性のためだけのものです。新しい実装は絶対に2桁の年を生成してはなりません (MUST NOT)。
4. Local Time (ローカル時刻)
4.1. Coordinated Universal Time (UTC) (協定世界時)
ローカルタイムゾーンの夏時間ルールは非常に複雑で、予測不可能な時期に現地の法律に基づいて変更される可能性があるため、真の相互運用性は協定世界時 (Coordinated Universal Time, UTC) を使用することで最もよく達成されます。本仕様は、ローカルタイムゾーンルールには対応していません。
UTCを使用する理由:
- ✅ 世界統一の時間基準
- ✅ 夏時間の影響を受けない
- ✅ 政治的決定の影響を受けない
- ✅ 相互運用性を保証
4.2. Local Offsets (ローカルオフセット)
ローカル時刻とUTCの間のオフセットは、多くの場合有用な情報です。例えば、電子メール (RFC2822, [IMAIL-UPDATE]) では、ローカルオフセットは迅速な応答の可能性を判断するための有用なヒューリスティックを提供します。過去にアルファベット文字列でローカルオフセットをラベル付けしようとした試みは、相互運用性の低下をもたらしました [IMAIL], [HOST-REQ]。その結果、RFC2822 [IMAIL-UPDATE] は数値オフセットを必須としました。
数値オフセットの計算
数値オフセットは 「ローカル時刻マイナスUTC」 として計算されます。したがって、ローカル時刻からオフセットを減算することで、UTCでの等価時刻を決定できます。
例1:
ローカル時刻: 18:50:00-04:00
UTC時刻: 18:50:00 - (-04:00) = 18:50:00 + 04:00 = 22:50:00Z
検証: 東部夏時間 (EDT) はUTC-4
例2:
ローカル時刻: 15:30:00+08:00
UTC時刻: 15:30:00 - (+08:00) = 15:30:00 - 08:00 = 07:30:00Z
検証: 中国標準時 (CST) はUTC+8
重要な注意事項
注意: ISO 8601に従い、数値オフセットはUTCから整数分だけ異なるタイムゾーンのみを表します。ただし、多くの歴史的なタイムゾーンはUTCから非整数分異なります。このような歴史的タイムスタンプを正確に表現するには、アプリケーションはそれらを表現可能なタイムゾーンに変換しなければなりません (MUST)。
歴史的タイムゾーンの例:
19世紀後半の一部のローカル時刻:
- アムステルダム: UTC+00:19:32
- パリ: UTC+00:09:21
これらはRFC 3339で正確に表現できず、最も近い分に丸める必要があります
4.3. Unknown Local Offset Convention (未知のローカルオフセット規則)
UTCでの時刻は既知であるが、ローカル時刻へのオフセットが未知である場合、これはオフセット "-00:00" で表すことができます。これは、"Z" または "+00:00" のオフセットとは意味的に異なります。後者は、UTCが指定された時刻の優先参照点であることを意味します。RFC2822 [IMAIL-UPDATE] は、電子メールに対して同様の規則を説明しています。
3つの表現の区別
Z または +00:00:
2002-07-15T10:30:00Z
2002-07-15T10:30:00+00:00
意味: この時刻はUTC時刻であり、UTCが優先参照点です
-00:00:
2002-07-15T10:30:00-00:00
意味: UTC時刻は10:30:00ですが、ローカルタイムゾーンオフセットは未知です
(タイムゾーン設定を知らないシステムからの可能性があります)
使用シナリオ:
シナリオ1: サーバーログ、UTCであることが既知 → Zを使用
シナリオ2: デバイス生成タイムスタンプ、デバイスはUTCだがローカルゾーンを知らない → -00:00を使用
シナリオ3: 明示的にロンドン (GMT) → +00:00を使用
4.4. Unqualified Local Time (非限定ローカル時刻)
現在インターネットに接続されている多数のデバイスは、内部クロックをローカル時刻で実行しており、UTCを認識していません。インターネットは仕様を設計する際に現実を受け入れる伝統を持っていますが、これは相互運用性を犠牲にして行われるべきではありません。非限定ローカルタイムゾーンの解釈は、地球上の約23/24で失敗するため、
必須要件
インターネットプロトコルは完全修飾されたタイムスタンプを生成しなければなりません (MUST)。
これは、インターネットプロトコルがタイムゾーン情報なしでローカル時刻を使用してはならない (MUST NOT) ことを意味します。
誤った例:
❌ 2002-07-15T10:30:00 (タイムゾーン情報なし)
正しい例:
✅ 2002-07-15T10:30:00Z (UTC)
✅ 2002-07-15T10:30:00+08:00 (明示的なタイムゾーン)
✅ 2002-07-15T10:30:00-00:00 (UTCだがゾーンは未知)
相互運用性の問題
非限定ローカル時刻が使用された場合:
送信者: 2002-07-15T10:30:00 (ニューヨークのローカル時刻、実際はUTC-4)
東京の受信者: 東京時間 (UTC+9) と誤解釈
時刻差エラー: 13時間!
実装の推奨事項
システム設計
# 推奨: 常にUTCで時刻を保存
def store_timestamp():
utc_time = datetime.now(timezone.utc)
return utc_time.isoformat() # 2024-12-21T10:30:00+00:00
# 表示時: ユーザーのローカルタイムゾーンに変換
def display_timestamp(utc_time, user_timezone):
local_time = utc_time.astimezone(user_timezone)
return local_time.isoformat()
データベースストレージ
-- 推奨: TIMESTAMP WITH TIME ZONEを使用
CREATE TABLE events (
id SERIAL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 避ける: TIMESTAMP WITHOUT TIME ZONE (曖昧さを引き起こす)
重要な原則: 内部的にはUTCで保存し、表示時にローカルタイムゾーンに変換します。データ交換にタイムゾーン情報のないタイムスタンプを決して使用しないでください。
5. Date and Time format (日付と時刻の形式)
このセクションでは、日付と時刻の形式の望ましい品質について説明し、インターネット上で使用するためのISO 8601のプロファイルを定義します。
5.1. Ordering (順序付け)
日付と時刻のコンポーネントが最も精度の低いものから最も精度の高いものへと順序付けされている場合、有用な特性が達成されます。日付と時刻のタイムゾーンが同じであり(例:すべてUTC)、同じ文字列を使用して表現され(例:すべて"Z"またはすべて"+00:00")、すべての時刻が同じ数の小数秒桁を持つと仮定すると、日付と時刻の文字列は文字列としてソート(例:Cのstrcmp()関数を使用)でき、時間順のシーケンスが生成されます。オプションの句読点の存在は、この特性を損ないます。
例:
正しい順序(年-月-日 時:分:秒):
2002-01-15T10:00:00Z
2002-07-20T15:30:00Z
2002-12-31T23:59:59Z
誤った形式(月/日/年)は正しくソートできません:
01/15/2002 10:00:00
12/31/2002 23:59:59 ← 文字列ソートでは7月より前に配置される
07/20/2002 15:30:00
5.2. Human Readability (人間の可読性)
人間の可読性は、インターネットプロトコルの価値ある特徴であることが証明されています。人間が読めるプロトコルは、telnetがテストクライアントとして十分であることが多く、ネットワークアナライザーがプロトコルの知識で変更される必要がないため、デバッグのコストを大幅に削減します。一方、人間の可読性は、相互運用性の問題を引き起こすことがあります。
問題の例:
❌ "10/11/1996" はグローバル交換には完全に不適切
米国: 1996年10月11日
欧州: 1996年11月10日
❌ 月の略語の翻訳
英語: "Jan", "Feb", "Mar"
フランス語: "Jan", "Fév", "Mar" ← 相互運用性を損なう
すべての国の慣習に従って読める日付と時刻の形式は存在しないため、インターネットクライアントは、日付を地域に適した表示形式に変換する準備をしなければなりません(SHOULD)。これには、UTCをローカル時刻に変換することが含まれる場合があります。
5.3. Rarely Used Options (めったに使用されないオプション)
めったに使用されないオプションを含む形式は、相互運用性の問題を引き起こす可能性があります。これは、めったに使用されないオプションはアルファまたはベータテストで使用される可能性が低いため、解析のバグが発見される可能性が低いためです。相互運用性のために、めったに使用されないオプションは可能な限り必須にするか省略する必要があります。
以下に定義する形式には、めったに使用されないオプションが1つだけ含まれています:秒の小数部分 (Fractions of a Second)。これは、日付/時刻スタンプの厳密な順序付けを必要とするアプリケーションや、異常な精度要件を持つアプリケーションでのみ使用されることが予想されます。
5.4. Redundant Information (冗長情報)
日付/時刻形式に冗長情報が含まれている場合、冗長情報が相関しない可能性が導入されます。例えば、日付/時刻形式に曜日を含めると、曜日が間違っているが日付が正しい、またはその逆の可能性が導入されます。日付から曜日を計算することは難しくないため(付録Bを参照)、曜日は日付/時刻形式に含めるべきではありません。
問題の例:
❌ "Monday, 2002-07-16T10:00:00Z"
問題: 2002年7月16日は実際には火曜日で、月曜日ではありません
曜日と日付が一致しない場合、どちらを信頼すべきですか?
✅ "2002-07-16T10:00:00Z"
解決策: 曜日を省略し、必要に応じて計算します
5.5. Simplicity (簡潔性)
ISO 8601 [ISO8601] で指定されている日付と時刻の形式の完全なセットは、複数の表現と部分的な表現を提供しようとするため、非常に複雑です。付録Aには、ISO 8601の完全な構文をABNFに翻訳する試みが含まれています。インターネットプロトコルはやや異なる要件を持ち、簡潔性は重要な特性であることが証明されています。さらに、インターネットプロトコルは通常、真の相互運用性を達成するためにデータの完全な仕様を必要とします。したがって、ISO 8601の完全な構文は、ほとんどのインターネットプロトコルには複雑すぎると見なされます。
以下のセクションでは、インターネット上で使用するためのISO 8601のプロファイルを定義します。これはISO 8601拡張形式の一貫したサブセットです。簡潔性は、ほとんどのフィールドと句読点を必須にすることで達成されます。
5.6. Internet Date/Time Format (インターネット日付時刻形式)
以下のISO 8601 [ISO8601] 日付のプロファイルは、インターネット上の新しいプロトコルで使用されるべきです(SHOULD)。これは、[ABNF]で定義された構文記述表記を使用して指定されます。
ABNF構文定義
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = "." 1*DIGIT
time-numoffset = ("+" / "-") time-hour ":" time-minute
time-offset = "Z" / time-numoffset
partial-time = time-hour ":" time-minute ":" time-second
[time-secfrac]
full-date = date-fullyear "-" date-month "-" date-mday
full-time = partial-time time-offset
date-time = full-date "T" full-time
重要な注意事項
大文字小文字の区別: [ABNF]とISO8601に従い、この構文の"T"および"Z"文字は、それぞれ小文字の"t"または"z"を代替的に使用することもできます。
大文字小文字が重要な環境(XMLなど)でこの形式を使用する仕様は、日付時刻構文で使用される文字'T'と'Z'が常に大文字でなければならないように、日付時刻構文をさらに制限することができます(MAY)。この形式を生成するアプリケーションは大文字を使用すべきです(SHOULD)。
区切り文字: ISO 8601は、"T"で区切られた日付と時刻を定義しています。この構文を使用するアプリケーションは、可読性のために、(例えば)スペース文字で区切られたfull-dateとfull-timeを指定することを選択できます(MAY)。
形式の例
標準形式:
2002-07-15T10:30:00Z
2002-07-15T10:30:00.123Z
2002-07-15T10:30:00+08:00
2002-07-15T10:30:00-04:00
小数秒付き:
2002-07-15T10:30:00.123456Z
2002-07-15T10:30:00.52Z
可読性の高いバリアント(非標準だが許可):
2002-07-15 10:30:00Z
2002-07-15t10:30:00z
5.7. Restrictions (制限)
文法要素date-mdayは、現在の月内の日数を表します。最大値は月と年に基づいて変化します:
| 月番号 | 月/年 | 最大date-mday |
|---|---|---|
| 01 | 1月 (January) | 31 |
| 02 | 2月、平年 (February, normal) | 28 |
| 02 | 2月、閏年 (February, leap year) | 29 |
| 03 | 3月 (March) | 31 |
| 04 | 4月 (April) | 30 |
| 05 | 5月 (May) | 31 |
| 06 | 6月 (June) | 30 |
| 07 | 7月 (July) | 31 |
| 08 | 8月 (August) | 31 |
| 09 | 9月 (September) | 30 |
| 10 | 10月 (October) | 31 |
| 11 | 11月 (November) | 30 |
| 12 | 12月 (December) | 31 |
閏秒
文法要素time-secondは、閏秒が発生する月の末尾で値"60"を持つことができます(MAY)。閏秒は、UTCを地球の自転時刻に近づけるために使用されます。閏秒の詳細については、付録Dを参照してください。
閏秒の例:
1990-12-31T23:59:60Z ✅ 有効(1990年12月31日に閏秒が発生)
1990-12-31T23:59:61Z ❌ 無効(最大は60)
1990-06-15T23:59:60Z ❌ 無効(閏秒は月末のみ)
5.8. Examples (例)
以下は、有効なRFC 3339日付時刻スタンプの例です:
1985-04-12T23:20:50.52Z
表現: 1985年4月12日 23:20:50.52 UTC
1996-12-19T16:39:57-08:00
表現: 1996年12月19日 16:39:57 太平洋標準時(PST)
等価UTC: 1996-12-20T00:39:57Z
1990-12-31T23:59:60Z
表現: 1990年12月31日の閏秒
1990-12-31T15:59:60-08:00
表現: 1990年12月31日の閏秒、PSTで表現
等価UTC: 1990-12-31T23:59:60Z
1937-01-01T12:00:27.87+00:20
表現: 1937年1月1日 12:00:27.87、UTC+00:20
(歴史的タイムゾーンの例)
無効な例
❌ 1985-04-12 (時刻が欠落)
❌ 23:20:50.52Z (日付が欠落)
❌ 1985-04-12 23:20:50.52Z (スペースではなく'T'を使用すべき、ただし一部の実装は許可)
❌ 1985-04-32T23:20:50.52Z (無効な日付:4月に32日はない)
❌ 1985-02-29T23:20:50.52Z (無効な日付:1985年は閏年ではない)
実装の推奨事項: 常に標準形式を生成し('T'区切り文字と大文字の'Z'を使用)、寛容に解析します('t'、'z'、および場合によってはスペース区切り文字を受け入れる)。
6. References (参考文献)
Normative References (規範的参考文献)
[ABNF]
Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 2234, November 1997.
[ISO8601]
"Data elements and interchange formats -- Information interchange -- Representation of dates and times", ISO 8601:1988(E), International Organization for Standardization, June 1988.
注記: ISO 8601:1988はISO 8601:2000によって更新され、さらにISO 8601:2004によって更新されました。RFC 3339は1988年版に基づいています。
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
Informative References (参考情報的参考文献)
[IMAIL]
Crocker, D., "Standard for the Format of Arpa Internet Text Messages", STD 11, RFC 822, August 1982.
[IMAIL-UPDATE]
Resnick, P., "Internet Message Format", RFC 2822, April 2001.
[HOST-REQ]
Braden, R., "Requirements for Internet Hosts -- Application and Support", STD 3, RFC 1123, October 1989.
[NTP]
Mills, D., "Network Time Protocol (Version 3) Specification, Implementation and Analysis", RFC 1305, March 1992.
[ITU-R-TF]
"Standard-frequency and time-signal emissions", ITU-R Recommendation TF.460-4, 1986.
[UNICODE]
The Unicode Consortium, "The Unicode Standard", Version 3.0, Reading, MA, Addison-Wesley, 2000, ISBN 0-201-61633-5.
関連RFC文書
前身文書
- RFC 822 - 電子メール形式(ARPAインターネットテキストメッセージの標準形式)
- RFC 2822 - インターネットメッセージ形式(RFC 822を更新)
関連規格
- RFC 2234 - ABNF構文仕様
- RFC 2119 - RFCキーワード定義
- RFC 1305 - ネットワークタイムプロトコル(NTP)
後続の更新
- RFC 4287 - Atomシンジケーション形式(RFC 3339タイムスタンプを使用)
- RFC 7493 - I-JSONメッセージ形式(RFC 3339を推奨)
- RFC 8259 - JSONデータ交換形式(日時にRFC 3339を推奨)
外部規格
ISO 8601シリーズ
- ISO 8601:1988 - 本RFCが基づくバージョン
- ISO 8601:2000 - 第1次改訂
- ISO 8601:2004 - 第2次改訂
- ISO 8601-1:2019 - 最新版、第1部:基本規則
- ISO 8601-2:2019 - 最新版、第2部:拡張
その他の関連規格
- IETF BCP 14 - RFC 2119とRFC 8174からなる最良の現行慣行
- W3C Date and Time Formats - ISO 8601とRFC 3339に基づく
- ECMA-262 - JavaScript日時文字列形式(簡略化されたISO 8601に基づく)
実用的な応用
RFC 3339形式は以下で広く使用されています:
インターネットプロトコル:
- HTTP Dateヘッダー(ただし、HTTPはRFC 7231で定義された異なる形式を使用)
- Atom/RSSフィードタイムスタンプ
- JSON APIタイムスタンプ
- XMLスキーマdateTime型
プログラミング言語:
- JavaScript
Date.toISOString() - Python
datetime.isoformat() - Java
Instant.toString() - Go
time.RFC3339
データベース:
- PostgreSQL
TIMESTAMPTZ - MongoDB
ISODate - MySQL
TIMESTAMPwith timezone
注記: RFC 3339はISO 8601:1988に基づいていますが、ISO 8601の完全な実装ではなく、プロファイル(サブセット)です。RFC 3339は、インターネットプロトコルの相互運用性を確保するために、より厳格で簡略化されています。
7. Security Considerations (セキュリティに関する考慮事項)
この文書は日付と時刻を表現するための形式のみを指定しているため、ここで議論されるセキュリティ問題は、非同期のクロックがセキュリティ機能に与える影響に限定されます。
非同期クロックのセキュリティリスク
1. 証明書検証の失敗
非同期のクロックは、証明書が誤って期限切れまたはまだ有効でないように見える原因となります。
リスク例:
クライアントのクロック: 2002-07-14T10:00:00Z (1日進んでいる)
証明書の有効期間:
Not Before: 2002-07-15T00:00:00Z
Not After: 2003-07-15T23:59:59Z
結果: クライアントが有効な証明書を拒否 ❌
逆のケース:
クライアントのクロック: 2003-07-20T10:00:00Z (2年遅れている)
証明書の有効期間:
Not After: 2003-07-15T23:59:59Z (すでに期限切れ)
結果: クライアントが期限切れの証明書を受け入れる ⚠️ セキュリティリスク!
2. タイムスタンプ検証のバイパス
多くのセキュリティプロトコルは、リプレイ攻撃を防ぐためにタイムスタンプに依存しています。
リプレイ攻撃の例:
攻撃者が傍受した正当なリクエスト:
POST /transfer HTTP/1.1
Timestamp: 2002-07-15T10:00:00Z
Amount: $1000
Signature: valid_signature
サーバーのクロックが1時間遅れている場合、攻撃者はこのリクエストを再送信できます
3. 信頼できない監査ログ
ログのタイムスタンプが不正確な場合、セキュリティ監査とフォレンジック分析が不可能または信頼できなくなります。
問題シナリオ:
サーバーAのログ: 2002-07-15T10:00:00Z - 侵入検知
サーバーBのログ: 2002-07-15T09:45:00Z - 異常なログイン(実際にはAより後だが、クロックが遅い)
正確な攻撃タイムラインを確立できない ❌
保護の推奨事項
NTP (Network Time Protocol) の使用
すべてのインターネット接続システムは、NTPまたは類似の時刻同期プロトコルを使用すべきです(SHOULD)。
NTP設定例:
# NTPサーバーを設定
ntpdate -u time.nist.gov
# ntpdデーモンを有効化
systemctl enable ntpd
systemctl start ntpd
# 同期状態を確認
ntpq -p
タイムスタンプ許容範囲
タイムスタンプを検証する際に合理的な許容範囲ウィンドウを実装します。
実装例:
def is_timestamp_valid(timestamp, max_age_seconds=300):
"""タイムスタンプが許容可能な時間ウィンドウ内にあるか検証"""
now = datetime.now(timezone.utc)
tolerance = timedelta(seconds=max_age_seconds)
# ±5分のクロックスキューを許容
if abs(now - timestamp) > tolerance:
return False
return True
信頼できる時刻ソースの使用
推奨される公開NTPサーバー:
time.nist.gov (米国国立標準技術研究所)
time.google.com (Google)
time.apple.com (Apple)
time.cloudflare.com (Cloudflare)
pool.ntp.org (NTP Pool Project)
証明書検証のベストプラクティス
# 証明書を検証する際にクロックスキューを考慮
def verify_certificate(cert, clock_tolerance=timedelta(minutes=5)):
now = datetime.now(timezone.utc)
# Not Beforeを寛容にチェック
if now < (cert.not_before - clock_tolerance):
raise CertificateNotYetValid()
# Not Afterを厳密にチェック(セキュリティ優先)
if now > cert.not_after:
raise CertificateExpired()
タイムゾーン関連のセキュリティ問題
1. タイムゾーン混乱攻撃
タイムゾーンの取り扱いが一貫していない場合、セキュリティバイパスにつながる可能性があります。
脆弱性の例:
ユーザー送信: 2002-07-15T23:00:00-08:00
システムAが解析: 2002-07-16T07:00:00Z (正しい)
システムBが解析: 2002-07-15T23:00:00Z (誤り、タイムゾーンを無視)
システムBがアクセス制御判断に使用される場合、不正アクセスを許可する可能性があります
2. 夏時間の境界
夏時間の移行時に曖昧さやセキュリティ問題が発生する可能性があります。
リスクのある時刻:
2002年3月10日 午前2:00 → 午前3:00 (1時間スキップ)
問題: 午前2:30が存在しない場合、このタイムスタンプをどう扱うか?
2002年11月3日 午前2:00 → 午前1:00 (1時間繰り返し)
問題: 午前1:30が2回発生する場合、どちらが正しいか?
RFC 3339の解決策: UTCオフセットを使用して曖昧さを排除:
✅ 2002-11-03T01:30:00-05:00 (EDT、夏時間終了前)
✅ 2002-11-03T01:30:00-04:00 (EST、夏時間終了後)
閏秒のセキュリティへの影響
まれではありますが、閏秒の不適切な処理は問題を引き起こす可能性があります。
潜在的な問題:
1990-12-31T23:59:60Z (閏秒)
システムが閏秒をサポートしていない場合:
- 有効なタイムスタンプを拒否する可能性
- ソートエラーを引き起こす可能性
- 1秒の時間差を引き起こす可能性
推奨事項:
# 閏秒を寛容に処理
def parse_timestamp(ts_string):
try:
return datetime.fromisoformat(ts_string)
except ValueError as e:
# 閏秒かどうかをチェック(秒が60)
if ':60Z' in ts_string or ':60+' in ts_string or ':60-' in ts_string:
# 60秒を次の分の00秒に変換
ts_string = ts_string.replace(':60', ':59')
return datetime.fromisoformat(ts_string) + timedelta(seconds=1)
raise
セキュリティチェックリスト
RFC 3339タイムスタンプを実装する際に確認すること:
- NTPを使用してシステムクロックを同期する
- 内部ストレージと比較には常にUTCを使用する
- タイムスタンプを検証する際に合理的な許容範囲を実装する
- タイムゾーンオフセットを正しく処理する
- すべての時刻関連のセキュリティイベントを記録する
- システムクロックの精度を定期的に監査する
- 証明書検証でクロックスキューを考慮する
- リプレイ攻撃保護を実装する(nonce + タイムスタンプ)
- 寛容に解析し、厳密に生成する
- エッジケースをテストする(閏秒、閏年、月末)
重要な原則: 重要なセキュリティ決定にクライアント提供のタイムスタンプに依存しないでください。常にサーバー側の信頼できる時刻ソースを使用してください。
Appendix A. ISO 8601 Collected ABNF
この情報は1988年版のISO 8601に基づいています。2000年の改訂版では若干の変更がある可能性があります。
説明
ISO 8601は、定義する日付と時刻の形式に対して正式な文法を指定していません。以下は、ISO 8601から正式な文法を作成する試みです。これは情報提供のみを目的としており、エラーが含まれている可能性があります。ISO 8601が権威ある参照であり続けます。
曖昧さと解釈
ISO 8601の曖昧さのため、いくつかの解釈が必要であったことに注意してください:
-
基本形式と拡張形式の混合: ISO 8601は、基本形式と拡張形式の混合が許可されているかどうか明確ではありません。この文法は混合を許可します。
-
24時間: ISO 8601は、分と秒が0の場合にのみ時刻24が許可されるかどうか明確ではありません。この文法は、任意のコンテキストで時刻24が許可されると仮定します。
-
日付制限: date-mdayに関するセクション5.7の制限が適用されます。
-
"T"区切り文字: ISO 8601は、特定の状況下で"T"を省略できることを指定しています。この文法は曖昧さを避けるために"T"を必須とします。
-
小数点: ISO 8601は(セクション5.3.1.3で)小数部分が1未満の場合、"0"で始まらなければならないことを要求しています。ISO 8601の付録B.2は"0"で始まらない小数部分の例を示しています。この文法はセクション5.3.1.3が正しく、付録B.2が誤りであると仮定します。
完全なISO 8601 ABNF文法
date-century = 2DIGIT ; 00-99
date-decade = DIGIT ; 0-9
date-subdecade = DIGIT ; 0-9
date-year = date-decade date-subdecade
date-fullyear = date-century date-year
date-month = 2DIGIT ; 01-12
date-wday = DIGIT ; 1-7 ; 1 is Monday, 7 is Sunday
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31
date-yday = 3DIGIT ; 001-365, 001-366
date-week = 2DIGIT ; 01-52, 01-53
datepart-fullyear = [date-century] date-year ["-"]
datepart-ptyear = "-" [date-subdecade ["-"]]
datepart-wkyear = datepart-fullyear / datepart-ptyear
dateopt-century = "-" / date-century
dateopt-fullyear = "-" / datepart-fullyear
dateopt-year = "-" / (date-year ["-"])
dateopt-month = "-" / (date-month ["-"])
dateopt-week = "-" / (date-week ["-"])
datespec-full = datepart-fullyear date-month ["-"] date-mday
datespec-year = date-century / dateopt-century date-year
datespec-month = "-" dateopt-year date-month [["-"] date-mday]
datespec-mday = "--" dateopt-month date-mday
datespec-week = datepart-wkyear "W"
(date-week / dateopt-week date-wday)
datespec-wday = "---" date-wday
datespec-yday = dateopt-fullyear date-yday
date = datespec-full
/ datespec-year
/ datespec-month
/ datespec-mday
/ datespec-week
/ datespec-wday
/ datespec-yday
time-hour = 2DIGIT ; 00-24
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60
time-fraction = ("," / ".") 1*DIGIT
time-numoffset = ("+" / "-") time-hour [[":"] time-minute]
time-zone = "Z" / time-numoffset
timeopt-hour = "-" / (time-hour [":"])
timeopt-minute = "-" / (time-minute [":"])
timespec-hour = time-hour [[":"] time-minute [[":"] time-second]]
timespec-minute = timeopt-hour time-minute [[":"] time-second]
timespec-second = "-" timeopt-minute time-second
timespec-base = timespec-hour / timespec-minute / timespec-second
time = timespec-base [time-fraction] [time-zone]
iso-date-time = date "T" time
RFC 3339 vs 完全なISO 8601
RFC 3339はISO 8601の制限されたサブセットであり、完全な実装ではありません:
| 機能 | ISO 8601 | RFC 3339 |
|---|---|---|
| 基本形式 (20020715) | ✅ サポート | ❌ 非サポート |
| 拡張形式 (2002-07-15) | ✅ サポート | ✅ サポート |
| 週日付 (2002-W29-1) | ✅ サポート | ❌ 非サポート |
| 序数日付 (2002-196) | ✅ サポート | ❌ 非サポート |
| 部分日付 (2002-07) | ✅ サポート | ❌ 非サポート |
| 24時間 (2002-07-16T24:00:00) | ✅ サポート | ❌ 非サポート |
| タイムゾーン"Z" | ✅ サポート | ✅ サポート |
| 数値タイムゾーンオフセット | ✅ サポート | ✅ サポート(必須) |
| 小数秒 | ✅ サポート | ✅ サポート |
RFC 3339簡略化の理由
RFC 3339が簡略化されたサブセットを選択した理由:
- 相互運用性: 実装の差異を減らす
- 明確性: 曖昧さを回避
- 完全性: 完全な日付時刻情報を要求
- 簡潔性: 実装とテストが容易
注記: 完全なISO 8601機能(週日付など)が必要な場合は、ISO 8601標準を直接参照してください。RFC 3339は、インターネットプロトコルで最も一般的なタイムスタンプのユースケースに焦点を当てています。
Appendix B. Day of the Week (曜日)
この付録では、任意のグレゴリオ暦の日付から曜日を計算する方法を示します。これは、RFC 3339が曜日情報を含まない理由を理解するために重要です—なぜなら、正確に計算できるからです。
Zeller's Congruence (ツェラーの公式)
曜日を計算するために一般的に使用されるアルゴリズムは、1882年にChristian Zellerによって発明されたZeller's Congruence(ツェラーの合同式)です。
公式
h = (q + ⌊13(m+1)/5⌋ + K + ⌊K/4⌋ + ⌊J/4⌋ - 2J) mod 7
ここで:
h: 曜日 (0 = 土曜日, 1 = 日曜日, 2 = 月曜日, ..., 6 = 金曜日)q: 月の日 (1-31)m: 月 (3-14、3 = 3月, 4 = 4月, ..., 12 = 12月, 13 = 1月, 14 = 2月)K: 世紀内の年 (year % 100)J: 世紀 (⌊year/100⌋)⌊x⌋: 床関数
注記: 1月と2月は前年の13番目と14番目の月として扱われます。
Python実装
def day_of_week_zeller(year, month, day):
"""
ツェラーの公式を使用して曜日を計算
戻り値: 0=土曜日, 1=日曜日, ..., 6=金曜日
"""
# 1月と2月は前年の13月と14月として扱う
if month < 3:
month += 12
year -= 1
q = day
m = month
K = year % 100
J = year // 100
h = (q + (13 * (m + 1)) // 5 + K + K // 4 + J // 4 - 2 * J) % 7
# 一般的な形式に変換: 0=月曜日, ..., 6=日曜日
# Zeller: 0=土, 1=日, 2=月, 3=火, 4=水, 5=木, 6=金
# 調整: 0=月, 1=火, 2=水, 3=木, 4=金, 5=土, 6=日
return (h + 5) % 7
def day_name(year, month, day):
"""曜日の名前を返す"""
days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
return days[day_of_week_zeller(year, month, day)]
# 例
print(day_name(2002, 7, 15)) # Monday
print(day_name(2000, 1, 1)) # Saturday
print(day_name(1999, 12, 31)) # Friday
より簡単なアルゴリズム
プログラミング実装の場合、より直感的なアルゴリズムを使用できます:
def day_of_week_simple(year, month, day):
"""
簡略化された曜日計算
戻り値: 0=月曜日, ..., 6=日曜日
"""
# 各月前の累積日数(平年)
t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
if month < 3:
year -= 1
y = year % 100
c = year // 100
return (y + y // 4 + c // 4 - 2 * c + t[month - 1] + day) % 7
JavaScript実装
function dayOfWeek(year, month, day) {
// JavaScript Dateオブジェクトが自動的に曜日を計算
const date = new Date(year, month - 1, day);
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'];
return days[date.getDay()];
}
// 例
console.log(dayOfWeek(2002, 7, 15)); // Monday
検証例
| 日付 | 曜日 | 検証済み |
|---|---|---|
| 2002-07-15 | 月曜日 (Monday) | ✅ |
| 2000-01-01 | 土曜日 (Saturday) | ✅ |
| 1999-12-31 | 金曜日 (Friday) | ✅ |
| 1985-04-12 | 金曜日 (Friday) | ✅ |
| 1990-12-31 | 月曜日 (Monday) | ✅ |
RFC 3339が曜日を含まない理由
1. 冗長情報
曜日は日付から正確に計算できるため、含めると潜在的な不整合が発生します:
誤った例:
"Monday, 2002-07-16T10:00:00Z"
問題: 2002-07-16は実際には火曜日で、月曜日ではありません
曜日と日付のどちらを信頼すべきですか?
2. 複雑性の増加
パーサーは曜日と日付間の検証と不整合を処理する必要があります。
3. ローカライゼーションの問題
曜日の名前は言語によって異なります:
英語: Monday, Tuesday, Wednesday, ...
フランス語: Lundi, Mardi, Mercredi, ...
日本語: 月曜日, 火曜日, 水曜日, ...
4. 時点に影響しない
曜日は時点の決定に影響せず、人間の可読性のためだけのものです。
推奨事項
曜日を表示する必要がある場合:
from datetime import datetime
# RFC 3339タイムスタンプを解析
timestamp = "2002-07-15T10:00:00Z"
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
# 曜日を計算して表示
day_name = dt.strftime('%A')
print(f"{timestamp} is a {day_name}")
# 出力: 2002-07-15T10:00:00Z is a Monday
結論: 曜日は日付から正確かつ決定論的に計算できるため、タイムスタンプ形式に含めることは不必要であるだけでなく、有害です。
Appendix D. Leap Seconds (閏秒)
この付録では、RFC 3339における閏秒の概念、歴史、および取り扱いについて詳しく説明します。
閏秒とは?
閏秒は、UTCを地球の自転と同期させるために、時折協定世界時(UTC)に追加される1秒です。
なぜ閏秒が必要か?
原子時(TAI):
- 原子時計に基づく、非常に安定
- 1秒 = 9,192,631,770回のセシウム原子振動
- 決して変化しない
地球の自転:
- 完全に均一ではない
- 潮汐摩擦などの影響を受ける
- 徐々に遅くなる(1世紀あたり約1.4ms/日)
- 速度は予測不可能
閏秒の仕組み
正の閏秒の例
通常の月末:
23:59:58
23:59:59
00:00:00 (次の日)
正の閏秒がある月末:
23:59:58
23:59:59
23:59:60 ← 閏秒!
00:00:00 (次の日)
RFC 3339表現
1990-12-31T23:59:60Z ✅ 有効(1990年12月31日に閏秒)
2012-06-30T23:59:60Z ✅ 有効(2012年6月30日に閏秒)
2015-06-30T23:59:60Z ✅ 有効(2015年6月30日に閏秒)
2016-12-31T23:59:60Z ✅ 有効(2016年12月31日に閏秒)
歴史的閏秒
1972年のUTC導入以来:
日付 UTC時刻 TAI-UTC
1972-06-30 23:59:60Z +11秒
1972-12-31 23:59:60Z +12秒
1990-12-31 23:59:60Z +26秒
2012-06-30 23:59:60Z +35秒
2015-06-30 23:59:60Z +36秒
2016-12-31 23:59:60Z +37秒(最新)
注記:
- 閏秒は6月30日または12月31日にのみ追加
- 1972年以降27回の閏秒
- 最新は2016年12月31日
プログラミング上の考慮事項
問題:ほとんどのシステムは閏秒をサポートしていない
# 閏秒の寛容な解析
def parse_rfc3339_with_leap_seconds(timestamp_str):
if ':60' in timestamp_str:
timestamp_str = timestamp_str.replace(':60', ':59')
dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
return dt + timedelta(seconds=1)
else:
return datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
RFC 3339仕様
構文要件
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
- 秒の値は60であることができます(MAY)
- 月末でのみ
- 公表された閏秒でなければならない
重要ポイント: RFC 3339は閏秒を許可していますが、ほとんどの実装は実用的な互換性のために次の秒にマッピングします。