C# DateTimeOffsetの使い方を徹底解説|DateTimeとの違い・変換・タイムゾーン対応までわかる
はじめに
C#で日時を扱うとき、多くの人が最初に使うのはDateTimeです。しかし、Webアプリケーション、API、ログ、海外ユーザー向けサービス、クラウド環境などでは、DateTimeだけではタイムゾーンやUTCとの時差を正しく扱いきれない場面があります。
そこで重要になるのがDateTimeOffsetです。
DateTimeOffsetは、単なる日時だけでなく「UTCから何時間ずれているか」というオフセット情報を一緒に保持できる型です。たとえば、次のような値を表現できます。
C#2026-06-12T10:30:00+09:00
これは「2026年6月12日 10時30分」であり、さらに「UTCより9時間進んでいる」という意味を持ちます。
この記事では、C#のDateTimeOffsetについて、基本的な使い方からDateTimeとの違い、変換方法、タイムゾーン対応、実務での使いどころ、よくある落とし穴まで詳しく解説します。
1. C#のDateTimeOffsetとは?まず押さえる基本
1-1. DateTimeOffsetは「日時+UTCオフセット」を扱う型
DateTimeOffsetは、日時とUTCオフセットをセットで扱うための構造体です。
たとえば、次のような値を表せます。
C#var dateTimeOffset = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
Console.WriteLine(dateTimeOffset);
// 2026/06/12 10:30:00 +09:00
この値は「2026年6月12日 10時30分、UTC+09:00」という意味です。
UTCに変換すると、次のようになります。
C#Console.WriteLine(dateTimeOffset.UtcDateTime);
// 2026/06/12 1:30:00
日本時間の10時30分は、UTCでは1時30分です。このように、DateTimeOffsetを使うと「ローカルで見えている日時」と「UTC基準の時刻」を安全に結びつけて扱えます。
1-2. Offset・UtcDateTime・LocalDateTime・DateTimeプロパティの意味
DateTimeOffsetには、日時変換でよく使うプロパティがいくつかあります。
C#var dto = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
Console.WriteLine(dto.Offset);
Console.WriteLine(dto.UtcDateTime);
Console.WriteLine(dto.LocalDateTime);
Console.WriteLine(dto.DateTime);
それぞれの意味は次のとおりです。
Offsetは、UTCからの時差を表します。日本時間であれば通常は+09:00です。
C#Console.WriteLine(dto.Offset);
// 09:00:00
UtcDateTimeは、UTCに変換したDateTimeを返します。
C#Console.WriteLine(dto.UtcDateTime);
// 2026/06/12 1:30:00
LocalDateTimeは、実行環境のローカルタイムゾーンに変換したDateTimeを返します。サーバーのタイムゾーン設定に依存するため、クラウド環境では注意が必要です。
DateTimeプロパティは、DateTimeOffsetが持っている日付と時刻部分だけをDateTimeとして取り出します。ただし、オフセット情報は失われます。
C#DateTime dateTime = dto.DateTime;
DateTimeに変換すると、+09:00のような情報は消えるため、安易に使うとタイムゾーン関連のバグにつながります。
1-3. DateTimeOffsetが必要になる代表的なケース
DateTimeOffsetが特に役立つのは、次のようなケースです。
APIで日時を返す場合は、クライアントがどのタイムゾーンで表示すべきか判断しやすくなります。
JSON{
"createdAt": "2026-06-12T10:30:00+09:00"
}
ログや監査証跡では、イベントが発生した瞬間を正確に記録する必要があります。DateTimeOffsetならUTCとの対応関係を保持できるため、後から時刻を比較しやすくなります。
海外ユーザー向けサービスでは、ユーザーごとの現地時刻を表示したり、UTCで保存した時刻を各地域の時刻に変換したりする必要があります。
予約、締切、通知などでは「ユーザーにとっての現地時刻」が重要になります。たとえば、日本のユーザーにとっての9時と、ニューヨークのユーザーにとっての9時は、UTC上ではまったく別の時刻です。
1-4. DateTimeOffsetだけでは「タイムゾーン名」までは保持しない点に注意
DateTimeOffsetは非常に便利ですが、重要な注意点があります。
DateTimeOffsetが保持するのは「UTCからのオフセット」であり、「タイムゾーン名」ではありません。
たとえば、次の値を見てください。
C#2026-06-12T10:30:00+09:00
これはUTC+09:00であることは分かります。しかし、それが日本時間なのか、韓国時間なのか、別のUTC+09:00地域なのかまでは分かりません。
つまり、DateTimeOffsetだけでは次のような情報は保持できません。
Asia/Tokyo
Tokyo Standard Time
America/New_York
Europe/London
タイムゾーン名やサマータイムのルールまで扱いたい場合は、TimeZoneInfoと組み合わせる必要があります。
2. DateTimeOffsetとDateTimeの違い
2-1. DateTimeはDateTimeKindでUTC・Local・Unspecifiedを表す
DateTimeは、日時を表す基本的な型です。
C#DateTime now = DateTime.Now;
DateTime utcNow = DateTime.UtcNow;
DateTimeにはKindというプロパティがあり、その値によって日時の種類を表します。
C#Console.WriteLine(DateTime.Now.Kind); // Local
Console.WriteLine(DateTime.UtcNow.Kind); // Utc
DateTimeKindには次の3種類があります。
C#DateTimeKind.Local
DateTimeKind.Utc
DateTimeKind.Unspecified
Localはローカル時刻、UtcはUTC時刻、Unspecifiedはどちらとも明示されていない時刻です。
問題は、DateTimeそのものには具体的なUTCオフセットが含まれないことです。Kindで「UTCかローカルか」は分かっても、「UTC+09:00なのか」「UTC-05:00なのか」といった情報は値の中に直接保持されません。
2-2. DateTimeOffsetはUTCからの時差を値として保持する
一方、DateTimeOffsetはUTCからのオフセットを値として保持します。
C#var jstTime = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
Console.WriteLine(jstTime);
// 2026/06/12 10:30:00 +09:00
この場合、日時だけでなく+09:00という情報も保持されています。
そのため、UTCに変換して比較することが簡単です。
C#Console.WriteLine(jstTime.UtcDateTime);
// 2026/06/12 1:30:00
同じ瞬間を別のオフセットで表すこともできます。
C#var utcTime = jstTime.ToOffset(TimeSpan.Zero);
Console.WriteLine(utcTime);
// 2026/06/12 1:30:00 +00:00
この2つは表示上の日時は違いますが、UTC上では同じ瞬間を表しています。
2-3. DateTimeとDateTimeOffsetの比較表
| 項目 | DateTime | DateTimeOffset |
|---|---|---|
| 保持する情報 | 日時 | 日時+UTCオフセット |
| UTCとの関係 | Kindで表す | Offsetで明示的に保持 |
| タイムゾーン名 | 保持しない | 保持しない |
| APIレスポンス | 曖昧になりやすい | オフセット付きで表現しやすい |
| ログ・監査証跡 | 注意が必要 | 向いている |
| UTC変換 | 可能 | 可能 |
| 海外対応 | バグが出やすい | 扱いやすい |
| サマータイム対応 | 単体では不可 | 単体では不可。TimeZoneInfoが必要 |
DateTimeOffsetは、DateTimeよりも常に優れているというわけではありません。しかし、UTCとの対応関係が重要な場面では、DateTimeOffsetの方が安全です。
2-4. DateTimeでは起こりやすいタイムゾーン関連のバグ
DateTimeでよくあるバグは、UTCなのかローカル時刻なのか分からなくなることです。
たとえば、次のようなコードがあるとします。
C#DateTime createdAt = DateTime.Now;
この値は実行環境のローカル時刻です。開発環境が日本時間で、本番サーバーがUTC設定の場合、保存される時刻が変わる可能性があります。
また、DateTimeKind.Unspecifiedの値を変換するときも注意が必要です。
C#var dateTime = new DateTime(2026, 6, 12, 10, 30, 0);
Console.WriteLine(dateTime.Kind);
// Unspecified
この値はUTCなのか日本時間なのか分かりません。後からToUniversalTime()を呼ぶと、実行環境のローカル時刻として扱われる可能性があり、意図しない変換になることがあります。
C#DateTime utc = dateTime.ToUniversalTime();
日時データが曖昧なまま処理されると、数時間ずれたデータが保存されたり、通知時刻がずれたり、締切判定が間違ったりします。
2-5. DateTimeOffsetを使うべき場面・DateTimeで十分な場面
DateTimeOffsetを使うべき場面は、主に次のようなケースです。
APIで日時を外部に公開する場合、ログや監査証跡を記録する場合、複数タイムゾーンのユーザーを扱う場合、データベースに発生時刻を保存する場合、UTCとの対応関係を明確にしたい場合です。
C#public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
一方で、DateTimeで十分な場面もあります。
たとえば、誕生日のようにタイムゾーン変換すべきではない日付、カレンダー上の日付だけを扱う場合、アプリケーション内部で完全にUTCに統一していてオフセット表示が不要な場合などです。
ただし、日付だけを扱うなら、.NETのバージョンや用途によってはDateOnlyを検討する方が適切な場合もあります。
3. C#でDateTimeOffsetを作成する方法
3-1. DateTimeOffset.NowとDateTimeOffset.UtcNowの違い
現在時刻を取得するには、DateTimeOffset.NowまたはDateTimeOffset.UtcNowを使います。
C#var now = DateTimeOffset.Now;
var utcNow = DateTimeOffset.UtcNow;
Console.WriteLine(now);
Console.WriteLine(utcNow);
DateTimeOffset.Nowは、実行環境のローカルタイムゾーンに基づく現在時刻を返します。
C#var now = DateTimeOffset.Now;
Console.WriteLine(now);
// 例: 2026/06/12 10:30:00 +09:00
DateTimeOffset.UtcNowは、UTCの現在時刻を返します。オフセットは+00:00です。
C#var utcNow = DateTimeOffset.UtcNow;
Console.WriteLine(utcNow);
// 例: 2026/06/12 1:30:00 +00:00
実務では、システム内部の記録時刻にはUtcNowを使うことが多いです。
C#public class Order
{
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
Nowはサーバーのローカル設定に依存するため、複数環境で動くアプリケーションでは注意が必要です。
3-2. コンストラクタで日時とオフセットを指定する
任意の日時とオフセットを指定してDateTimeOffsetを作成できます。
C#var jst = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
TimeSpan.FromHours(9)は、UTC+09:00を表します。
UTCの日時を作成する場合は、オフセットにTimeSpan.Zeroを指定します。
C#var utc = new DateTimeOffset(
2026, 6, 12, 1, 30, 0,
TimeSpan.Zero
);
Console.WriteLine(utc);
// 2026/06/12 1:30:00 +00:00
分単位のオフセットも指定できます。
C#var india = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(5.5)
);
Console.WriteLine(india);
// 2026/06/12 10:30:00 +05:30
3-3. DateTimeからDateTimeOffsetを作成する
DateTimeからDateTimeOffsetを作成することもできます。
C#DateTime dateTime = new DateTime(2026, 6, 12, 10, 30, 0);
var dto = new DateTimeOffset(dateTime, TimeSpan.FromHours(9));
Console.WriteLine(dto);
// 2026/06/12 10:30:00 +09:00
この方法では、DateTimeがどのタイムゾーンの日時なのかを明示的にオフセットで補います。
ただし、DateTime.KindがUtcの場合は、基本的にオフセットはTimeSpan.Zeroと考えるべきです。
C#DateTime utcDateTime = new DateTime(
2026, 6, 12, 1, 30, 0,
DateTimeKind.Utc
);
var dto = new DateTimeOffset(utcDateTime);
Console.WriteLine(dto);
// 2026/06/12 1:30:00 +00:00
DateTimeKind.Unspecifiedの値から作る場合は、その日時がどの地域の時刻なのかを必ず明確にしてください。
3-4. 文字列からDateTimeOffset.Parse・TryParseで変換する
文字列からDateTimeOffsetに変換するには、ParseまたはTryParseを使います。
C#var text = "2026-06-12T10:30:00+09:00";
DateTimeOffset dto = DateTimeOffset.Parse(text);
Console.WriteLine(dto);
// 2026/06/12 10:30:00 +09:00
ユーザー入力や外部APIの値を扱う場合は、例外を避けるためにTryParseを使う方が安全です。
C#var text = "2026-06-12T10:30:00+09:00";
if (DateTimeOffset.TryParse(text, out var dto))
{
Console.WriteLine(dto);
}
else
{
Console.WriteLine("日時形式が正しくありません。");
}
Parseは変換に失敗すると例外を投げます。一方、TryParseは成功・失敗をboolで返します。
3-5. ISO 8601形式の日時文字列を扱う方法
APIやJSONで日時を扱う場合は、ISO 8601形式がよく使われます。
2026-06-12T10:30:00+09:00
2026-06-12T01:30:00Z
末尾のZはUTCを意味します。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
Console.WriteLine(utc);
// 2026/06/12 1:30:00 +00:00
ISO 8601形式で文字列化する場合は、ToString("O")を使うと便利です。Oはラウンドトリップ形式で、日時とオフセットを失わずに表現できます。
C#var dto = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
string text = dto.ToString("O");
Console.WriteLine(text);
// 2026-06-12T10:30:00.0000000+09:00
APIレスポンスやログ出力では、この形式を使うと安全です。
4. DateTimeOffsetのよく使う操作
4-1. 日時の加算・減算を行う
DateTimeOffsetでは、DateTimeと同じように日時の加算・減算ができます。
C#var now = DateTimeOffset.UtcNow;
var tomorrow = now.AddDays(1);
var nextHour = now.AddHours(1);
var nextMonth = now.AddMonths(1);
Console.WriteLine(tomorrow);
Console.WriteLine(nextHour);
Console.WriteLine(nextMonth);
TimeSpanを使って加算することもできます。
C#var dto = DateTimeOffset.UtcNow;
var result = dto + TimeSpan.FromMinutes(30);
Console.WriteLine(result);
減算も可能です。
C#var dto = DateTimeOffset.UtcNow;
var result = dto.AddDays(-7);
Console.WriteLine(result);
ただし、タイムゾーンのルールやサマータイムを考慮したい場合は、単純なAddHoursだけでは不十分なことがあります。特定地域の現地時刻として計算する場合は、TimeZoneInfoとの組み合わせを検討しましょう。
4-2. 2つのDateTimeOffsetを比較する
DateTimeOffset同士は比較できます。
C#var jst = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
var utc = new DateTimeOffset(
2026, 6, 12, 1, 30, 0,
TimeSpan.Zero
);
Console.WriteLine(jst == utc);
// True
この2つは表示上の日時とオフセットは違います。
2026-06-12 10:30:00 +09:00
2026-06-12 01:30:00 +00:00
しかし、UTC上では同じ瞬間を表しているため、比較結果はtrueになります。
並び替えもUTC基準で考えると分かりやすいです。
C#var list = new List<DateTimeOffset>
{
DateTimeOffset.Parse("2026-06-12T10:30:00+09:00"),
DateTimeOffset.Parse("2026-06-12T02:00:00Z"),
DateTimeOffset.Parse("2026-06-12T09:00:00+09:00")
};
var ordered = list.OrderBy(x => x).ToList();
foreach (var item in ordered)
{
Console.WriteLine(item);
}
同じ瞬間かどうかを比較したい場合は、通常の比較で問題ありません。オフセットまで完全に同じか確認したい場合は、EqualsExactを使います。
C#var a = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
var b = DateTimeOffset.Parse("2026-06-12T01:30:00+00:00");
Console.WriteLine(a.Equals(b)); // True
Console.WriteLine(a.EqualsExact(b)); // False
4-3. UTC時刻へ変換する
UTCへ変換するには、ToUniversalTime()またはUtcDateTimeを使います。
C#var jst = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
var utc = jst.ToUniversalTime();
Console.WriteLine(utc);
// 2026/06/12 1:30:00 +00:00
UtcDateTimeを使うと、UTCのDateTimeを取得できます。
C#DateTime utcDateTime = jst.UtcDateTime;
Console.WriteLine(utcDateTime);
Console.WriteLine(utcDateTime.Kind);
// Utc
アプリケーション内部で保存や比較を行う場合は、UTCにそろえると扱いやすくなります。
4-4. ローカル時刻へ変換する
ローカル時刻へ変換するには、ToLocalTime()を使います。
C#var utc = DateTimeOffset.UtcNow;
var local = utc.ToLocalTime();
Console.WriteLine(local);
ただし、ここでいうローカル時刻は「実行環境のローカルタイムゾーン」です。ユーザーの現地時刻とは限りません。
たとえば、サーバーがUTCで動いている場合、ToLocalTime()を呼んでも日本時間になるとは限りません。ユーザーごとのタイムゾーンに変換したい場合は、TimeZoneInfoを使います。
4-5. 任意のオフセットに変換する
任意のオフセットに変換するには、ToOffset()を使います。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var jst = utc.ToOffset(TimeSpan.FromHours(9));
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
ToOffset()は、同じ瞬間を別のオフセット表現に変換します。
C#var original = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
var converted = original.ToOffset(TimeSpan.Zero);
Console.WriteLine(converted);
// 2026/06/12 1:30:00 +00:00
時刻の見た目は変わりますが、UTC上の瞬間は同じです。
5. DateTimeOffsetとDateTimeの変換方法
5-1. DateTimeOffsetからDateTimeへ変換する
DateTimeOffsetからDateTimeへ変換するには、DateTimeプロパティを使います。
C#var dto = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
DateTime dateTime = dto.DateTime;
Console.WriteLine(dateTime);
// 2026/06/12 10:30:00
ただし、この変換ではオフセット情報が失われます。
C#Console.WriteLine(dto);
// 2026/06/12 10:30:00 +09:00
Console.WriteLine(dateTime);
// 2026/06/12 10:30:00
DateTimeだけを見ると、それが日本時間なのかUTCなのか分かりません。そのため、APIやデータベース保存の前に安易にDateTimeへ変換するのは避けた方が安全です。
5-2. DateTimeOffsetからUtcDateTimeへ変換する
UTCのDateTimeが必要な場合は、UtcDateTimeを使います。
C#var dto = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
DateTime utcDateTime = dto.UtcDateTime;
Console.WriteLine(utcDateTime);
// 2026/06/12 1:30:00
Console.WriteLine(utcDateTime.Kind);
// Utc
データベースにUTCとして保存したい場合や、システム内部の比較処理に使いたい場合に便利です。
C#entity.CreatedAtUtc = dto.UtcDateTime;
ただし、DateTimeとして保存するとオフセット情報は失われます。オフセットも保持したい場合は、保存先の型や設計を見直しましょう。
5-3. DateTimeOffsetからLocalDateTimeへ変換する
実行環境のローカル時刻に変換したDateTimeが必要な場合は、LocalDateTimeを使います。
C#var dto = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
DateTime localDateTime = dto.LocalDateTime;
Console.WriteLine(localDateTime);
Console.WriteLine(localDateTime.Kind);
// Local
ただし、LocalDateTimeはサーバーや実行環境のタイムゾーンに依存します。
Webアプリケーションでユーザーの現地時刻を表示したい場合、LocalDateTimeだけに頼るのは危険です。ユーザーのタイムゾーンを取得し、TimeZoneInfoで変換する方が適切です。
5-4. DateTimeからDateTimeOffsetへ変換する
DateTimeからDateTimeOffsetへ変換する方法はいくつかあります。
UTCのDateTimeなら、次のように扱えます。
C#DateTime utcDateTime = DateTime.UtcNow;
DateTimeOffset dto = new DateTimeOffset(utcDateTime);
Console.WriteLine(dto);
特定のオフセットを指定する場合は、次のようにします。
C#DateTime dateTime = new DateTime(2026, 6, 12, 10, 30, 0);
DateTimeOffset jst = new DateTimeOffset(
dateTime,
TimeSpan.FromHours(9)
);
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
このように、DateTimeがどのタイムゾーンの時刻なのか分かっている場合は、明示的にオフセットを指定するのが安全です。
5-5. DateTimeKindがUnspecifiedの場合の注意点
DateTimeKind.Unspecifiedは、日時変換で特に注意が必要です。
C#var dateTime = new DateTime(2026, 6, 12, 10, 30, 0);
Console.WriteLine(dateTime.Kind);
// Unspecified
この値は、UTCなのかローカル時刻なのか、型だけでは判断できません。
たとえば、この値が日本時間だと分かっているなら、次のように明示的にDateTimeOffsetへ変換します。
C#var jst = new DateTimeOffset(
dateTime,
TimeSpan.FromHours(9)
);
UTCとして扱いたい場合は、DateTime.SpecifyKindで明示する方法もあります。
C#var utcDateTime = DateTime.SpecifyKind(
dateTime,
DateTimeKind.Utc
);
var dto = new DateTimeOffset(utcDateTime);
重要なのは、Unspecifiedのまま曖昧に扱わないことです。外部システムから受け取った日時や古いデータベースの日時カラムでは、KindがUnspecifiedになりやすいため注意しましょう。
6. タイムゾーン対応でDateTimeOffsetを使う方法
6-1. オフセットとタイムゾーンの違い
DateTimeOffsetを使う上で、オフセットとタイムゾーンの違いは必ず理解しておきたいポイントです。
オフセットは、UTCからの時差です。
+09:00
+00:00
-05:00
一方、タイムゾーンは地域ごとの時刻ルールです。
Asia/Tokyo
America/New_York
Europe/London
オフセットは単なる時差ですが、タイムゾーンにはサマータイムなどのルールが含まれる場合があります。
たとえば、ニューヨークは時期によってUTC-05:00になったりUTC-04:00になったりします。つまり、同じAmerica/New_Yorkでも日付によってオフセットが変わることがあります。
そのため、サマータイムを正しく扱いたい場合は、単にTimeSpan.FromHours(-5)のように固定オフセットを指定するのではなく、TimeZoneInfoを使う必要があります。
6-2. TimeZoneInfoと組み合わせて時刻を変換する
TimeZoneInfoを使うと、特定のタイムゾーンへ日時を変換できます。
C#var utc = DateTimeOffset.UtcNow;
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
var jst = TimeZoneInfo.ConvertTime(utc, timeZone);
Console.WriteLine(jst);
Windows環境では、日本時間のタイムゾーンIDとしてTokyo Standard Timeが使われます。
LinuxやmacOSでは、環境によってAsia/Tokyoを使うことがあります。
C#var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
実行環境によって利用できるタイムゾーンIDが異なる場合があるため、クロスプラットフォームで動かすアプリケーションでは確認が必要です。
ユーザーのタイムゾーンに変換する場合は、ユーザープロフィールなどにタイムゾーンIDを保存しておく設計がよく使われます。
C#public class UserSettings
{
public string TimeZoneId { get; set; } = "Asia/Tokyo";
}
6-3. 日本時間(JST)とUTCを相互変換する
UTCから日本時間へ変換する例です。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var jst = utc.ToOffset(TimeSpan.FromHours(9));
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
日本時間は通常UTC+09:00で、サマータイムがないため、単純にToOffset(TimeSpan.FromHours(9))で扱えるケースが多いです。
ただし、タイムゾーンとして明示的に扱いたい場合は、TimeZoneInfoを使う方が設計として分かりやすくなります。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var tokyo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
var jst = TimeZoneInfo.ConvertTime(utc, tokyo);
Console.WriteLine(jst);
日本時間からUTCへ変換する場合は、ToUniversalTime()を使います。
C#var jst = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
var utc = jst.ToUniversalTime();
Console.WriteLine(utc);
// 2026/06/12 1:30:00 +00:00
6-4. 海外ユーザー向けに現地時刻を表示する
海外ユーザー向けサービスでは、データベースにはUTCで保存し、画面表示時にユーザーのタイムゾーンへ変換する設計がよく使われます。
C#var eventTimeUtc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var localTime = TimeZoneInfo.ConvertTime(eventTimeUtc, userTimeZone);
Console.WriteLine(localTime);
このようにすると、保存時刻はUTCで統一しつつ、表示時にはユーザーにとって分かりやすい現地時刻にできます。
ユーザーごとのタイムゾーンIDを保存しておくと、通知や予約機能でも便利です。
C#public class User
{
public string TimeZoneId { get; set; } = "America/New_York";
}
表示用の処理は、次のように関数化しておくと使いやすくなります。
C#public static DateTimeOffset ConvertToUserTime(
DateTimeOffset utcTime,
string timeZoneId)
{
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
return TimeZoneInfo.ConvertTime(utcTime, timeZone);
}
6-5. サマータイムを考慮する場合の注意点
サマータイムがある地域では、固定オフセットだけで処理するとバグが起こりやすくなります。
たとえば、ニューヨークを常に-05:00として扱うのは危険です。夏時間の期間は-04:00になるため、1時間ずれる可能性があります。
悪い例は次のようなコードです。
C#var newYorkTime = utc.ToOffset(TimeSpan.FromHours(-5));
このコードは、常にUTC-05:00として変換します。サマータイムを考慮しません。
正しく扱うには、タイムゾーンIDを使います。
C#var utc = DateTimeOffset.Parse("2026-07-01T12:00:00Z");
var newYork = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var local = TimeZoneInfo.ConvertTime(utc, newYork);
Console.WriteLine(local);
サマータイムを考慮すべき地域では、DateTimeOffsetのOffsetだけではなく、必ずタイムゾーン情報を組み合わせて処理しましょう。
7. 実務でのDateTimeOffsetの使いどころ
7-1. APIレスポンスで日時を返す場合
APIレスポンスで日時を返す場合、DateTimeOffsetは非常に便利です。
C#public class UserResponse
{
public int Id { get; set; }
public string Name { get; set; } = "";
public DateTimeOffset CreatedAt { get; set; }
}
JSONにすると、オフセット付きの形式で表現できます。
JSON{
"id": 1,
"name": "Taro",
"createdAt": "2026-06-12T10:30:00+09:00"
}
クライアント側は、この値を見ればUTCとの関係を判断できます。
APIでは、日時を返すときに次のような曖昧な形式は避けた方が安全です。
JSON{
"createdAt": "2026-06-12 10:30:00"
}
この形式では、UTCなのか日本時間なのか分かりません。APIでは、Zまたは+09:00のようなオフセットを含めることをおすすめします。
7-2. データベースに日時を保存する場合
データベースに日時を保存する場合は、次のどちらかの方針を決めておくことが重要です。
1つ目は、UTCで統一して保存する方法です。
C#entity.CreatedAt = DateTimeOffset.UtcNow;
この方法は、検索、比較、並び替えがしやすく、サーバー環境にも依存しにくいです。
2つ目は、オフセット付きで保存する方法です。
C#public DateTimeOffset CreatedAt { get; set; }
SQL Serverであればdatetimeoffset型のように、オフセット付きの日時を保存できる型があります。
ただし、データベースやORMによって日時型の扱いは異なります。DateTimeOffsetを使っていても、保存先のカラム型によってはオフセット情報が失われることがあります。
そのため、設計時には次の点を確認しましょう。
アプリケーション側の型
データベースのカラム型
ORMのマッピング
JSONやAPIでのシリアライズ形式
7-3. ログ・監査証跡・イベント時刻を記録する場合
ログや監査証跡では、「いつ発生したか」を正確に記録することが重要です。
C#public class AuditLog
{
public int Id { get; set; }
public string Action { get; set; } = "";
public DateTimeOffset OccurredAt { get; set; }
}
記録時にはUTCを使うと扱いやすくなります。
C#var log = new AuditLog
{
Action = "UserLogin",
OccurredAt = DateTimeOffset.UtcNow
};
UTCで統一しておけば、複数地域のサーバーやユーザーから発生したログを時系列に並べやすくなります。
ログを人間が読む画面では、必要に応じてユーザーのタイムゾーンに変換して表示します。
C#var displayTime = TimeZoneInfo.ConvertTime(
log.OccurredAt,
userTimeZone
);
7-4. 予約・締切・通知などユーザーの現地時刻が重要な場合
予約、締切、通知では、「UTC上の瞬間」と「ユーザーの現地時刻」の両方を意識する必要があります。
たとえば、ユーザーが「2026年6月12日 9時に通知してほしい」と設定した場合、その9時はユーザーのタイムゾーンに依存します。
C#public class NotificationSchedule
{
public int UserId { get; set; }
public DateTimeOffset ScheduledAtUtc { get; set; }
public string TimeZoneId { get; set; } = "";
}
保存時にはUTCに変換しておきます。
C#var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
var localDateTime = new DateTime(2026, 6, 12, 9, 0, 0);
var localOffset = new DateTimeOffset(
localDateTime,
userTimeZone.GetUtcOffset(localDateTime)
);
var utc = localOffset.ToUniversalTime();
ただし、サマータイムがある地域では、存在しない時刻や重複する時刻が発生することがあります。予約や通知のように現地時刻が重要な機能では、タイムゾーンIDを保存しておくことが大切です。
7-5. システム内部ではUTCで統一するべき理由
実務では、システム内部の時刻はUTCで統一するのが基本です。
理由は、比較や検索が簡単になるからです。
C#var now = DateTimeOffset.UtcNow;
var expiredItems = items
.Where(x => x.ExpiresAt <= now)
.ToList();
すべての時刻がUTCで保存されていれば、タイムゾーンの違いを気にせずに比較できます。
また、本番環境、開発環境、バッチサーバー、クラウド環境などでタイムゾーン設定が異なっていても、UTCで統一しておけば影響を受けにくくなります。
一方、画面表示やメール通知では、ユーザーにとって分かりやすい現地時刻に変換します。
C#var userLocalTime = TimeZoneInfo.ConvertTime(
utcTime,
userTimeZone
);
保存はUTC、表示はユーザーのタイムゾーン。この方針を徹底すると、日時関連のバグを大幅に減らせます。
8. DateTimeOffsetでよくあるエラー・落とし穴
8-1. Offsetだけ見てタイムゾーンを判断してしまう
DateTimeOffset.Offsetを見ると、UTCからの時差は分かります。
C#var dto = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
Console.WriteLine(dto.Offset);
// 09:00:00
しかし、+09:00だからといって、それが必ず日本時間とは限りません。
UTC+09:00の地域は複数あります。DateTimeOffsetはタイムゾーン名を保持していないため、Asia/Tokyoなのか、別の地域なのかは分かりません。
タイムゾーンを識別したい場合は、別途タイムゾーンIDを保存してください。
C#public class UserDateTime
{
public DateTimeOffset Value { get; set; }
public string TimeZoneId { get; set; } = "";
}
8-2. DateTimeプロパティを使ってオフセット情報を失う
DateTimeOffset.DateTimeを使うと、オフセット情報が失われます。
C#var dto = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
DateTime dateTime = dto.DateTime;
Console.WriteLine(dateTime);
// 2026/06/12 10:30:00
このDateTimeだけを見ると、UTCなのか日本時間なのか分かりません。
UTCのDateTimeが欲しい場合は、UtcDateTimeを使いましょう。
C#DateTime utc = dto.UtcDateTime;
ローカル時刻が欲しい場合は、LocalDateTimeを使います。
C#DateTime local = dto.LocalDateTime;
ただし、LocalDateTimeは実行環境のローカルタイムゾーンに依存します。
8-3. NowとUtcNowを混在させてしまう
DateTimeOffset.NowとDateTimeOffset.UtcNowを混在させると、コードの意図が分かりにくくなります。
C#var createdAt = DateTimeOffset.Now;
var checkedAt = DateTimeOffset.UtcNow;
DateTimeOffset同士の比較はUTC基準で行われるため、比較自体は可能です。しかし、保存される値の見た目が混在すると、ログやデータ確認時に混乱します。
実務では、保存用の時刻はUtcNowに統一するのがおすすめです。
C#var createdAt = DateTimeOffset.UtcNow;
var updatedAt = DateTimeOffset.UtcNow;
画面に表示するときだけ、ユーザーのタイムゾーンへ変換します。
8-4. 文字列変換時にオフセットが消える
日時を文字列に変換するとき、フォーマットによってはオフセット情報が消えます。
C#var dto = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
string text = dto.ToString("yyyy-MM-dd HH:mm:ss");
Console.WriteLine(text);
// 2026-06-12 10:30:00
この文字列には+09:00が含まれていません。後から読み直したとき、どのタイムゾーンの時刻なのか分からなくなります。
オフセットを保持したい場合は、O形式を使います。
C#string text = dto.ToString("O");
Console.WriteLine(text);
// 2026-06-12T10:30:00.0000000+09:00
APIやログでは、オフセットが消えない形式を選ぶことが重要です。
8-5. データベース保存時に型や形式を誤る
DateTimeOffsetを使っていても、データベース側の型が適切でないと、オフセット情報が失われることがあります。
たとえば、アプリケーション側ではDateTimeOffsetを使っているのに、データベース側でオフセットを保持しない日時型に保存すると、意図した情報が残らない場合があります。
C#public DateTimeOffset CreatedAt { get; set; }
保存先のカラム型、ORMの設定、シリアライズ形式を必ず確認しましょう。
実務では、次のどちらかに統一するのがおすすめです。
UTCに変換して保存する
オフセット付きで保存できる型に保存する
どちらを選ぶ場合でも、プロジェクト全体でルールを統一することが大切です。
9. DateTimeOffsetの実践コード例
9-1. 現在時刻をUTC付きで取得するコード
システム内部で使う現在時刻を取得する場合は、DateTimeOffset.UtcNowを使います。
C#DateTimeOffset now = DateTimeOffset.UtcNow;
Console.WriteLine(now);
// 例: 2026/06/12 1:30:00 +00:00
エンティティの作成日時にもよく使います。
C#public class Article
{
public int Id { get; set; }
public string Title { get; set; } = "";
public DateTimeOffset CreatedAt { get; set; }
}
var article = new Article
{
Title = "C# DateTimeOffsetの使い方",
CreatedAt = DateTimeOffset.UtcNow
};
更新日時にも同じ方針で使えます。
C#article.CreatedAt = DateTimeOffset.UtcNow;
9-2. 日本時間のDateTimeOffsetを作成するコード
日本時間のDateTimeOffsetを作るには、オフセットに+09:00を指定します。
C#var jst = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
文字列から作ることもできます。
C#var jst = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
Console.WriteLine(jst);
日本時間は通常UTC+09:00でサマータイムがないため、固定オフセットでも扱いやすい地域です。ただし、タイムゾーンIDとして管理したい場合はTimeZoneInfoを使いましょう。
9-3. UTCから日本時間へ変換するコード
UTCの日時を日本時間に変換するには、ToOffset()を使えます。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var jst = utc.ToOffset(TimeSpan.FromHours(9));
Console.WriteLine(jst);
// 2026/06/12 10:30:00 +09:00
TimeZoneInfoを使う場合は次のように書けます。
C#var utc = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
var tokyo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
var jst = TimeZoneInfo.ConvertTime(utc, tokyo);
Console.WriteLine(jst);
クロスプラットフォーム環境では、タイムゾーンIDが異なる場合があるため、実行環境に合わせて確認してください。
9-4. API向けにISO 8601形式で出力するコード
API向けに日時を文字列化する場合は、ToString("O")が便利です。
C#var dto = new DateTimeOffset(
2026, 6, 12, 10, 30, 0,
TimeSpan.FromHours(9)
);
string iso8601 = dto.ToString("O");
Console.WriteLine(iso8601);
// 2026-06-12T10:30:00.0000000+09:00
UTCで返す場合は、次のようにできます。
C#var utc = dto.ToUniversalTime();
string iso8601 = utc.ToString("O");
Console.WriteLine(iso8601);
// 2026-06-12T01:30:00.0000000+00:00
Z形式にしたい場合は、UtcDateTimeを使ってフォーマットする方法もあります。
C#string text = dto.UtcDateTime.ToString("O");
Console.WriteLine(text);
// 2026-06-12T01:30:00.0000000Z
APIでは、日時文字列にオフセットまたはUTCであることを示すZを含めるようにしましょう。
9-5. DateTimeOffsetを安全に比較するコード
日時を比較する場合は、DateTimeOffset同士で比較できます。
C#var deadline = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
var now = DateTimeOffset.UtcNow;
if (now > deadline)
{
Console.WriteLine("期限切れです。");
}
else
{
Console.WriteLine("期限内です。");
}
deadlineが+09:00で、nowが+00:00でも、比較はUTC基準で行われます。
明示的にUTCにそろえて比較したい場合は、次のように書いても構いません。
C#if (now.UtcDateTime > deadline.UtcDateTime)
{
Console.WriteLine("期限切れです。");
}
ただし、DateTimeOffset同士の比較で十分な場合が多いため、無理にDateTimeへ変換する必要はありません。
同じ瞬間かどうかを確認する例です。
C#var a = DateTimeOffset.Parse("2026-06-12T10:30:00+09:00");
var b = DateTimeOffset.Parse("2026-06-12T01:30:00Z");
Console.WriteLine(a == b);
// True
オフセットまで一致しているか確認する場合は、EqualsExactを使います。
C#Console.WriteLine(a.EqualsExact(b));
// False
10. DateTimeOffsetに関するよくある質問
10-1. DateTimeOffsetとDateTimeはどちらを使うべき?
API、ログ、監査証跡、データベース保存、複数タイムゾーン対応が必要な場合は、DateTimeOffsetを使うのがおすすめです。
C#public DateTimeOffset CreatedAt { get; set; }
一方、誕生日や記念日など、タイムゾーン変換すべきではない日付を扱う場合は、DateTimeやDateOnlyの方が適していることがあります。
判断基準は、「UTC上の瞬間として扱いたいか」です。
イベント発生時刻や作成日時のように、世界中で同じ瞬間を指す値ならDateTimeOffsetが向いています。日付そのものが重要な値なら、DateTimeやDateOnlyを検討しましょう。
10-2. DateTimeOffsetはタイムゾーンを保存できる?
DateTimeOffsetはタイムゾーン名を保存できません。
保存できるのは、日時とUTCオフセットです。
2026-06-12T10:30:00+09:00
この値には+09:00は含まれますが、Asia/Tokyoというタイムゾーン名は含まれません。
タイムゾーン名が必要な場合は、別のプロパティとして保存します。
C#public class Schedule
{
public DateTimeOffset StartAt { get; set; }
public string TimeZoneId { get; set; } = "Asia/Tokyo";
}
サマータイムや地域ごとの時刻ルールが重要な場合は、DateTimeOffsetだけでなくTimeZoneInfoも使いましょう。
10-3. UTCで保存するならDateTimeOffsetは不要?
UTCで保存するだけなら、DateTimeでも実現できます。
C#public DateTime CreatedAtUtc { get; set; }
ただし、DateTimeの場合はKindの扱いに注意が必要です。Utcであることをコードや命名規則で明確にしないと、後からローカル時刻と混同する可能性があります。
DateTimeOffsetを使ってUTC保存する場合は、オフセット+00:00として表現できます。
C#public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
APIや外部連携まで考えると、DateTimeOffsetの方が日時の意味を明確にしやすいです。
10-4. DateTimeOffset.NowとDateTimeOffset.UtcNowはどちらを使う?
基本的には、保存や比較に使う時刻はDateTimeOffset.UtcNowがおすすめです。
C#var now = DateTimeOffset.UtcNow;
DateTimeOffset.Nowは、実行環境のローカルタイムゾーンに依存します。
C#var localNow = DateTimeOffset.Now;
ローカル時刻として表示したい場合にはNowが便利なこともあります。しかし、WebアプリケーションやAPIでは、サーバーのローカル時刻がユーザーの現地時刻とは限りません。
そのため、実務では次の方針が安全です。
保存・比較・ログ記録はUtcNow
表示はユーザーのタイムゾーンへ変換
10-5. DateTimeOffsetをJSONで扱うときの注意点
DateTimeOffsetをJSONで扱う場合は、オフセットが失われない形式になっているか確認しましょう。
望ましい形式は次のようなものです。
JSON{
"createdAt": "2026-06-12T10:30:00+09:00"
}
またはUTCとして返す形式です。
JSON{
"createdAt": "2026-06-12T01:30:00Z"
}
避けたいのは、オフセットのない曖昧な形式です。
JSON{
"createdAt": "2026-06-12 10:30:00"
}
この形式では、クライアント側がUTCなのかローカル時刻なのか判断できません。
ASP.NET CoreでDateTimeOffsetをJSONに含める場合、通常はISO 8601形式でシリアライズされます。ただし、カスタムコンバーターや独自フォーマットを使っている場合は、オフセットが消えていないか確認しましょう。
C#public class Response
{
public DateTimeOffset CreatedAt { get; set; }
}
APIの日時形式はプロジェクト全体で統一し、ドキュメントにも明記しておくと安全です。
まとめ
DateTimeOffsetは、C#で日時とUTCオフセットを一緒に扱うための重要な型です。
DateTimeが日時そのものを表すのに対して、DateTimeOffsetは「その日時がUTCからどれだけずれているか」も保持できます。そのため、API、ログ、監査証跡、データベース保存、海外ユーザー対応など、UTCとの関係が重要な場面で特に役立ちます。
基本的な考え方は、システム内部ではUTCで統一し、表示時にユーザーのタイムゾーンへ変換することです。
C#var now = DateTimeOffset.UtcNow;
日本時間に変換する場合は、次のように書けます。
C#var jst = now.ToOffset(TimeSpan.FromHours(9));
ただし、DateTimeOffsetだけではタイムゾーン名やサマータイムのルールまでは保持できません。地域ごとの時刻ルールが必要な場合は、TimeZoneInfoと組み合わせる必要があります。
また、DateTimeOffset.DateTimeを使うとオフセット情報が失われるため、変換時には注意が必要です。APIやログで文字列化するときは、ToString("O")などを使ってオフセットが消えない形式にしましょう。
C#で日時を安全に扱うには、DateTime、DateTimeOffset、TimeZoneInfoの役割を理解し、用途に応じて使い分けることが大切です。DateTimeOffsetを正しく使えば、タイムゾーンによる時刻ずれや変換ミスを防ぎ、信頼性の高い日時処理を実装できます。

