はじめに 「ISO8601変換」は“システム同士の共通語”をしゃべるための必須スキル
API、バッチ連携、ログ、クラウドサービス。
システム同士が日時をやり取りするとき、ほぼ必ず出てくるのが「ISO8601形式の日時文字列」です。
例として、こんな形を見たことがあると思います。
2026-02-10T09:30:00+09:00
2026-02-10T00:30:00Z
これが ISO8601 形式です。
C# では、DateTime や DateTimeOffset をこの形式に変換したり、逆に文字列からパースしたりする機能が標準で用意されています。
ここでは、
「ISO8601って何者か」から始めて、
C#での変換方法、UTCとタイムゾーンの扱い、DateTime と DateTimeOffset の違い、
実務でのユーティリティ化まで、初心者向けにかみ砕いて説明していきます。
ISO8601とは何かをざっくり押さえる
典型的なISO8601の形
ISO8601は「日付と時間の表記ルール」を決めた国際規格です。
よく使う形は次のようなものです。
2026-02-10T09:30:00+09:00 ← 日本時間(UTC+9)
2026-02-10T00:30:00Z ← UTC(ZはZulu、つまりUTC)
ポイントは次の3つです。
年-月-日(YYYY-MM-DD)
Tで日付と時間を区切る
末尾にタイムゾーン(+09:00 や Z)を付ける
この「タイムゾーン情報が入っている」ことが、
システム間連携ではとても重要です。
C#でISO8601文字列に変換する基本
DateTime を ISO8601 文字列にする
一番簡単な方法は、ToString("o")(小文字のオー)を使うことです。
これは「ラウンドトリップ形式」と呼ばれ、ISO8601互換の形式を出してくれます。
using System;
DateTime now = DateTime.UtcNow;
string iso = now.ToString("o");
Console.WriteLine(now); // 2026/02/10 0:30:00
Console.WriteLine(iso); // 2026-02-10T00:30:00.1234567Z のような形
C#DateTime.UtcNow を使うと、Kind が Utc の DateTime になります。"o" フォーマットは、Kind が Utc の場合、末尾に Z を付けてくれます。
日本時間で ISO8601 を出したい場合は、まず JST に変換してから "o" を使います。
TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTime utcNow = DateTime.UtcNow;
DateTime jstNow = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jst);
string isoJst = jstNow.ToString("o");
Console.WriteLine(isoJst); // 2026-02-10T09:30:00.1234567+09:00 のような形
C#ここでの重要ポイントは、
「ToString("o") は Kind に応じて Z 付き/オフセット付きのISO8601を出してくれる」
ということです。
DateTimeOffset を使ったISO8601変換
タイムゾーン情報を含んだ日時には DateTimeOffset が向いている
DateTime は「その日時がどのタイムゾーンのものか」を完全には表現しきれません。
一方、DateTimeOffset は「日時+オフセット(例: +09:00)」をセットで持つ型です。
ISO8601でやり取りするなら、DateTimeOffset を使うと素直に扱えます。
using System;
DateTimeOffset nowJst = DateTimeOffset.Now;
string iso = nowJst.ToString("o");
Console.WriteLine(nowJst); // 2026/02/10 9:30:00 +09:00
Console.WriteLine(iso); // 2026-02-10T09:30:00.1234567+09:00
C#UTCにしたい場合は、UtcNow を使います。
DateTimeOffset nowUtc = DateTimeOffset.UtcNow;
string isoUtc = nowUtc.ToString("o");
Console.WriteLine(isoUtc); // 2026-02-10T00:30:00.1234567+00:00
C#DateTimeOffset の "o" も ISO8601互換の文字列を出してくれるので、
「APIに投げる」「ログに書く」といった用途にそのまま使えます。
実務では、
「外部システムとISO8601でやり取りする日時」は DateTimeOffset で持つ、
という設計にすると、タイムゾーンの混乱がかなり減ります。
ISO8601文字列から日時にパースする
DateTime.Parse / DateTimeOffset.Parse を使う
ISO8601形式の文字列は、Parse や ParseExact で DateTime / DateTimeOffset に変換できます。
using System;
string isoUtc = "2026-02-10T00:30:00Z";
DateTime dtUtc = DateTime.Parse(isoUtc, null, System.Globalization.DateTimeStyles.AdjustToUniversal);
Console.WriteLine(dtUtc); // 2026/02/10 0:30:00
Console.WriteLine(dtUtc.Kind); // Utc
C#DateTimeStyles.AdjustToUniversal を付けることで、
Z や +09:00 などのオフセットを解釈して、UTC にそろえてくれます。
DateTimeOffset を使う場合はもっと自然です。
string iso = "2026-02-10T09:30:00+09:00";
DateTimeOffset dto = DateTimeOffset.Parse(iso);
Console.WriteLine(dto); // 2026/02/10 9:30:00 +09:00
Console.WriteLine(dto.Offset); // 09:00:00
Console.WriteLine(dto.UtcDateTime); // 2026/02/10 0:30:00
C#ISO8601のオフセット情報をそのまま保持したいなら、DateTimeOffset を使うのがベストです。
実務で使えるISO8601ユーティリティを作る
「UTCで保存・ISO8601でやり取り」を前提にしたユーティリティ
よくあるパターンとして、
「DBにはUTCで保存し、APIではISO8601文字列でやり取りする」
という設計があります。
その場合のユーティリティ例を見てみましょう。
using System;
using System.Globalization;
public static class Iso8601Util
{
public static string ToIso8601Utc(DateTime utc)
{
if (utc.Kind != DateTimeKind.Utc)
{
utc = DateTime.SpecifyKind(utc, DateTimeKind.Utc);
}
return utc.ToString("o");
}
public static DateTime ParseIso8601ToUtc(string iso8601)
{
DateTime dt = DateTime.Parse(
iso8601,
CultureInfo.InvariantCulture,
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
return dt;
}
public static string ToIso8601Offset(DateTimeOffset dto)
{
return dto.ToString("o");
}
public static DateTimeOffset ParseIso8601ToOffset(string iso8601)
{
return DateTimeOffset.Parse(iso8601, CultureInfo.InvariantCulture);
}
}
C#使い方のイメージです。
DateTime utcNow = DateTime.UtcNow;
string iso = Iso8601Util.ToIso8601Utc(utcNow);
DateTime parsedUtc = Iso8601Util.ParseIso8601ToUtc(iso);
Console.WriteLine(iso);
Console.WriteLine(parsedUtc);
C#ここでの重要ポイントは、
「UTCとして扱う」「オフセット付きとして扱う」を
メソッド名と型で分けていることです。
DateTime(UTC前提)と DateTimeOffset(オフセット付き)を混ぜないことで、
「この日時は何を意味しているのか」がコードから読み取りやすくなります。
実務での注意点:フォーマットを“自分で組み立てない”
"yyyy-MM-ddTHH:mm:ssZ" を直書きする罠
ISO8601っぽい文字列を自分で組み立てることもできます。
DateTime utc = DateTime.UtcNow;
string iso = utc.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'");
C#一見これでも良さそうですが、
ミリ秒やオフセット、Kind の違いなどを考え始めると、
自前フォーマットはすぐに破綻します。
"o" フォーマットは、
「その DateTime / DateTimeOffset が持っている情報を、
ラウンドトリップ可能な形で出す」ことに特化しているので、
基本的には "o" を使うのが安全です。
どうしても仕様で「ミリ秒なし」「Z固定」などが決まっている場合だけ、
フォーマット文字列を自前で組み立てるようにしましょう。
まとめ 「ISO8601変換ユーティリティ」は“日時の共通フォーマットを一箇所に集約する装置”
ISO8601変換は、
単に「文字列にする/文字列から戻す」だけではなく、
「UTCか?ローカルか?オフセットは?どの型で持つ?」といった
日時の意味づけそのものと深く結びついています。
押さえておきたいポイントを整理すると、こうなります。
ISO8601は「YYYY-MM-DDThh:mm:ss±hh:mm / Z」のような国際標準の日時表記で、タイムゾーン情報を含められる。
C#では "o" フォーマットを使うことで、DateTime / DateTimeOffset をISO8601互換の文字列に安全に変換できる。
オフセット付きの日時を扱うなら DateTimeOffset が適しており、ISO8601との相性も良い。
パース時は Parse / ParseExact と DateTimeStyles を組み合わせて、「UTCにそろえるのか」「オフセットを保持するのか」をはっきりさせる。
自前でフォーマット文字列を組み立てるのは最小限にし、共通のISO8601ユーティリティにロジックを集約することで、システム全体の一貫性と安全性が高まる。
ここまで押さえておけば、
「なんとなく日時を文字列にしている」状態から一歩進んで、
“外部システムとも安心してやり取りできる、実務で使えるISO8601変換ユーティリティ”を
自分のC#コードの中に自然に組み込めるようになります。

