C# で DateTime.Now と DateTime.UtcNow を使い分ける基本
こんにちは。ディアシステム開発1部開発2課の鶴田です。
はじめに
「DateTime.Now を使っていたら、Azure にデプロイしたら時刻が 9 時間ずれた」——こういう経験、ありませんか?
DateTime.Now と DateTime.UtcNow の使い分けは、C# を書き始めてすぐにぶつかる疑問の一つです。プログラム内部の計算だけなら気にならないこともあります。ただ、「DB に保存する」「API のレスポンスとして返す」「画面に表示する」といった用途が絡んでくると、どちらを使うかで挙動が変わってきます。
この記事では、2 つのプロパティの違いを整理して、ローカル PC と Azure それぞれの環境での動き、そして用途ごとの使い分け方を紹介します。
対象読者:C# を書き始めたばかり〜書いて 1〜2 年程度のエンジニア
前提環境:.NET 6 以降(C# 10〜)
DateTime.Now と DateTime.UtcNow の違い
DateTime.Now はどの設定に依存しているのか
DateTime.Now は、実行環境の OS に設定されているタイムゾーンを使って現在時刻を返します。
- ローカル PC(Windows):「設定」→「時刻と言語」→「タイムゾーン」の値が使われます。日本語環境なら通常 JST(UTC+9)なので、
DateTime.Nowは JST の時刻を返します。 - Azure App Service / Azure Functions:デフォルトのタイムゾーンは UTC です。そのため、同じ
DateTime.Nowを呼んでもローカルとは 9 時間違う値が返ってきます。
ローカルでテストして OK だったのに、Azure にデプロイしたら時刻がずれる——というバグの正体がこれです。
DateTime.UtcNow は常に UTC
DateTime.UtcNow は協定世界時(UTC)を返します。OS のタイムゾーン設定に関わらず、ローカルでも Azure でも同じ基準の時刻を返してくれます。
2 つの違いは Kind プロパティにも表れます。
DateTime local = DateTime.Now;
Console.WriteLine(local.Kind); // Local
DateTime utc = DateTime.UtcNow;
Console.WriteLine(utc.Kind); // Utc
Kind が Local の値が示す時刻は、実行環境のタイムゾーン設定によって変わります。Kind が Utc の値はどこで実行しても同じ基準です。
Azure でタイムゾーンを変更するには
どうしても DateTime.Now で JST を返したい場合は、Azure の環境変数でタイムゾーンを設定できます。
| 環境変数名 | 対象プラン | 設定値(JST の場合) |
|---|---|---|
WEBSITE_TIME_ZONE | Windows プラン | Tokyo Standard Time |
TZ | Linux プラン | Asia/Tokyo |
Azure ポータルの「構成」→「アプリケーション設定」から追加できます。ただし、この設定を忘れると UTC に戻るので、チームで開発する場合は「UTC で統一して必要なときだけ変換する」方針の方が安全です。
こうした環境差を踏まえると、DateTime.UtcNow を基準にする方が扱いやすいケースがほとんどです。次のセクションでは、用途別の判断基準を整理します。
どちらを使うべきか
基本方針は「内部処理・保存・通信は UTC で統一し、ユーザーに見せるときだけローカルに変換する」です。
| 用途 | 推奨 | 理由 |
|---|---|---|
| DB への保存 | DateTime.UtcNow | UTC で保存しておくと、読み出し時の変換が一意になります |
| API レスポンス・ログ出力 | DateTime.UtcNow | サーバー間通信では UTC が標準的です |
| UI への表示 | UTC → ローカルに変換した値 | ユーザーが見る時刻は現地時刻の方が自然です |
| タイムゾーンをまたぐシステム | DateTimeOffset.UtcNow | オフセット情報を値に含めることで変換ミスを防げます |
DateTimeOffset は DateTime にオフセット情報(+09:00 など)を加えた型です。タイムゾーンをまたいだ処理が多い場合は最初から DateTimeOffset を使うと後から悩む場面が減ります。
実践コード例
UTC で保存してローカルで表示する
// DB に保存するときは UTC
DateTime savedAt = DateTime.UtcNow;
// ユーザーに表示するときはローカルに変換
DateTime displayAt = savedAt.ToLocalTime();
Console.WriteLine($"保存日時(UTC): {savedAt:u}");
Console.WriteLine($"表示日時(JST): {displayAt}");
ToLocalTime() は実行環境のタイムゾーンに変換します。Azure のデフォルト環境(UTC)では変換されないので、JST 表示が必要な場合は次のパターンを使います。
TimeZoneInfo で任意のタイムゾーンに変換する
DateTime utcNow = DateTime.UtcNow;
TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTime jstNow = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jst);
Console.WriteLine($"UTC: {utcNow:u}");
Console.WriteLine($"JST: {jstNow}");
FindSystemTimeZoneById の引数は OS によって異なります。Windows では "Tokyo Standard Time"、Linux では "Asia/Tokyo" を使います。Azure App Service は Windows プランと Linux プランで異なるので注意してください。
DateTimeOffset を使う
DateTimeOffset utcNow = DateTimeOffset.UtcNow;
TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTimeOffset jstNow = TimeZoneInfo.ConvertTime(utcNow, jst);
Console.WriteLine($"UTC: {utcNow}");
Console.WriteLine($"JST: {jstNow}");
DateTimeOffset はオフセット情報を値の中に持っているので、DB に保存した後でも「これは何時何分の何時間オフセット」という情報が失われません。
ハマりどころ・注意点
Kind が Unspecified になる問題
DateTimeKind には Local・Utc のほかに Unspecified(未指定)という値があります。これは「UTC なのかローカルなのか不明」という状態です。ORM や ADO.NET で DB から datetime 型の値を読み出すと、Kind が Unspecified になることがあります。Kind が不定の場合、変換メソッドの動作は呼び出す API によって異なり、意図しない結果になる場合があります。
// DB から取得した値(Kind は Unspecified)
DateTime fromDb = new DateTime(2026, 4, 26, 8, 0, 0);
// 危険:Kind が Unspecified のまま変換すると意図しない結果になる場合がある
DateTime wrong = fromDb.ToLocalTime();
// 安全:UTC と明示してから変換する
DateTime safe = DateTime.SpecifyKind(fromDb, DateTimeKind.Utc).ToLocalTime();
UTC で保存した値を読み出すときは DateTime.SpecifyKind で Utc を明示する習慣をつけておくと安心です。
ToLocalTime() と ToUniversalTime() の変換方向ミス
ToLocalTime() は UTC → ローカル、ToUniversalTime() はローカル → UTC です。方向を間違えると 9 時間ずれた値が生成されます。「Local はローカルに変換する(=UTC から)」と覚えると混乱しにくいです。
Azure Functions のタイマートリガー
Azure Functions のタイマートリガーに書く cron 式も UTC 基準です。JST の午前 9 時に実行したい場合は 0 0 0 * * *(UTC 0 時 = JST 9 時)と指定します。
まとめ
DateTime.Nowの返す時刻は OS のタイムゾーン設定に依存します。Azure のデフォルトは UTC なので、ローカルと挙動が異なります。- Azure でタイムゾーンを変えたい場合は
WEBSITE_TIME_ZONE(Windows プラン)またはTZ(Linux プラン)を設定します。 - DB・API・ログには
DateTime.UtcNowを使い、UI 表示のときだけローカルに変換する——この方針で統一しておくと、環境差によるバグが減ります。 - タイムゾーンをまたぐ処理が多い場合は
DateTimeOffsetの利用を検討してみてください。
日時のバグは「ローカルでは動いたのに…」という形で出やすく、原因の特定に時間がかかりがちです。最初から UTC を基準にする習慣をつけておくと、将来の自分を助けることになりますよ。