C# Tips | 日付・時間処理:ISO8601変換

C# C#
スポンサーリンク

はじめに 「ISO8601変換」は“システム同士の共通語”をしゃべるための必須スキル

API、バッチ連携、ログ、クラウドサービス。
システム同士が日時をやり取りするとき、ほぼ必ず出てくるのが「ISO8601形式の日時文字列」です。

例として、こんな形を見たことがあると思います。

2026-02-10T09:30:00+09:00
2026-02-10T00:30:00Z

これが ISO8601 形式です。
C# では、DateTimeDateTimeOffset をこの形式に変換したり、逆に文字列からパースしたりする機能が標準で用意されています。

ここでは、
「ISO8601って何者か」から始めて、
C#での変換方法、UTCとタイムゾーンの扱い、DateTimeDateTimeOffset の違い、
実務でのユーティリティ化まで、初心者向けにかみ砕いて説明していきます。


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 が UtcDateTime になります。
"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形式の文字列は、ParseParseExactDateTime / 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 / ParseExactDateTimeStyles を組み合わせて、「UTCにそろえるのか」「オフセットを保持するのか」をはっきりさせる。
自前でフォーマット文字列を組み立てるのは最小限にし、共通のISO8601ユーティリティにロジックを集約することで、システム全体の一貫性と安全性が高まる。

ここまで押さえておけば、
「なんとなく日時を文字列にしている」状態から一歩進んで、
“外部システムとも安心してやり取りできる、実務で使えるISO8601変換ユーティリティ”を
自分のC#コードの中に自然に組み込めるようになります。

タイトルとURLをコピーしました