C#のDateTime.Nowは、日本時間とは限らない
ログの時刻や申請日時を記録するとき、C#では DateTime.Now を使うのがいちばん手軽です。ただ、DateTime.Now が返すのは「そのPCのタイムゾーンでの今」です。
社内のPCでは正しかった時刻が、クラウドのサーバに載せたら9時間ずれた
クラウドのサーバ(Azure・AWS・Linuxのコンテナなど)は、タイムゾーンがUTC(協定世界時)になっていることが多く、そのままでは日本時間より9時間前の時刻が記録されます。
本記事では、C#でPCの設定に左右されずに日本標準時(JST)を取得する方法を紹介します。自作の立替精算システムで、申請・承認の日時、通知、操作ログの時刻に使っている部品です。
立替精算システムで使ったところ
私が自作した立替精算システムは、社内のWindows PC 1台で動かす作りですが、導入先によってはサーバのタイムゾーンが日本になっていないこともあります。また、データベースをSQLite・PostgreSQL・SQL Serverから選べる設計にしているため、どこで動かしても日本時間になるようにしています。

通知の画面です。「承認されました」「経理確認が完了しました」などの日時を、日本時間で表示しています。
日本時間を取る方法の比較
| 書き方 | 得られるもの | 注意 |
|---|---|---|
| DateTime.Now | PCのタイムゾーンでの今 | サーバの設定で変わる |
| DateTime.UtcNow.AddHours(9) | 日本時間の今 | 今の日本にはサマータイムが無いので、これで正しい時刻になる。Kind は Utc のまま(後で説明) |
| DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(9)) | +09:00 付きの今 | 時差の情報も一緒に持てる |
| TimeZoneInfo.FindSystemTimeZoneById(…) | 指定したタイムゾーンの今 | IDにWindows形式(Tokyo Standard Time)とIANA形式(Asia/Tokyo)の2種類がある。動かす環境によっては見つからない |
TimeZoneInfo は、サマータイムのある国の時刻を扱うときには欠かせません。ただ、日本時間だけでよいなら、今の日本にはサマータイムが無い(1948〜1951年に行われたことはあります)ので「UTCに9時間を足す」だけで正しい時刻になります。OSのタイムゾーンのデータ(Linuxでは tzdata)や、IDの形式を変換するICUというライブラリにも頼らずに済むため、立替精算システムではこの方法にしています。
C#で日本時間(JST)を取得するサンプルコード
動作確認は Windows 11・.NET 10 です(.NET 8 以降で動きます)。Visual Studio でコンソールアプリを作り、Program.cs に貼り付けて実行してください。.NET 10 なら、ファイルに保存して dotnet run ファイル名.cs でも実行できます。
// 日本時間(JST)を取得する
Console.WriteLine($"DateTime.Now :{DateTime.Now:yyyy/MM/dd HH:mm:ss}(PC のタイムゾーンしだい)");
Console.WriteLine($"DateTime.UtcNow :{DateTime.UtcNow:yyyy/MM/dd HH:mm:ss}(協定世界時 UTC)");
Console.WriteLine($"JST(UTC+9) :{JstNow():yyyy/MM/dd HH:mm:ss}");
Console.WriteLine($"今日(JST) :{JstToday():yyyy/MM/dd}");
Console.WriteLine($"Kind :{JstNow().Kind}");
// 比較:DateTimeOffset で +09:00 付きの時刻にする
var offset = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(9));
Console.WriteLine($"DateTimeOffset :{offset:yyyy/MM/dd HH:mm:ss zzz}");
// 比較:TimeZoneInfo でタイムゾーンを探す(ID は Windows 形式と IANA 形式の 2 種類。環境によっては見つからない)
foreach (var id in new[] { "Tokyo Standard Time", "Asia/Tokyo" })
{
try
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(id);
var t = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
Console.WriteLine($"TimeZoneInfo({id}):{t:yyyy/MM/dd HH:mm:ss}");
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine($"TimeZoneInfo({id}):見つかりません");
}
}
/// <summary>
/// 日本時間(UTC+9)の今。今の日本にはサマータイムが無いので、UTC に 9 時間を足せばよい。
/// Kind は Unspecified にしておく(データベースの「タイムゾーンなし」の列にそのまま入れられる)。
/// </summary>
static DateTime JstNow() =>
DateTime.SpecifyKind(DateTime.UtcNow.AddHours(9), DateTimeKind.Unspecified);
/// <summary>日本時間の今日。</summary>
static DateOnly JstToday() => DateOnly.FromDateTime(JstNow());実行結果
DateTime.Now :2026/10/29 05:00:05(PC のタイムゾーンしだい)
DateTime.UtcNow :2026/10/28 20:00:05(協定世界時 UTC)
JST(UTC+9) :2026/10/29 05:00:05
今日(JST) :2026/10/29
Kind :Unspecified
DateTimeOffset :2026/10/29 05:00:05 +09:00
TimeZoneInfo(Tokyo Standard Time):2026/10/29 05:00:05
TimeZoneInfo(Asia/Tokyo):2026/10/29 05:00:05日時は、実行したときのものが表示されます。
このPCはタイムゾーンが日本なので、DateTime.Now とJSTが同じ時刻になっています。タイムゾーンがUTCのサーバで動かすと、DateTime.Now の行だけが9時間前の時刻になり、JSTの行は変わりません。
最近の.NET(.NET 6以降)は、WindowsでもIANA形式のID(Asia/Tokyo)でタイムゾーンを探せるため、この実行結果では両方とも見つかっています。ただ、ICUを使わない設定(グローバリゼーションのインバリアントモードやNLSモード)で動かすと、Windowsでは「Asia/Tokyo」が見つかりません。Linuxでも、タイムゾーンのデータ(tzdata)が入っていない小さなコンテナでは見つからないことがあります。このように動かす環境しだいで結果が変わる書き方は、できるだけ避けています。
コードの説明
DateTime.UtcNow.AddHours(9)で、UTC(協定世界時)に9時間を足して日本時間にしています。DateTimeには、その時刻がUTCなのか、PCのローカル時刻なのか、どちらでもないのかを表すKindがあります。UtcNowから作ると、9時間を足してもKindはUtcのままなので、DateTime.SpecifyKind(..., DateTimeKind.Unspecified)で「タイムゾーンを持たない日本時間」にしています。- PostgreSQL(Npgsql 6以降)では、
KindがUtcの値はtimestamp with time zoneの列用と決まっていて、timestamp without time zoneの列には書き込めません。Unspecifiedにしておけば、そのまま保存できます。立替精算システムでは、PostgreSQLのときは日時の列をすべてtimestamp without time zoneにする設計です。 DateOnly.FromDateTimeは、日付だけが欲しいとき(今日が何日か)に使います。
「今」を差し替えられるようにしておく
立替精算システムでは、時刻の取得をインターフェースにしています。
public interface IClock
{
/// <summary>現在日時(JST)。</summary>
DateTime Now { get; }
/// <summary>今日の日付(JST)。</summary>
DateOnly Today { get; }
}
public sealed class JstClock : IClock
{
private const int JstOffsetHours = 9;
public DateTime Now =>
DateTime.SpecifyKind(DateTime.UtcNow.AddHours(JstOffsetHours), DateTimeKind.Unspecified);
public DateOnly Today => DateOnly.FromDateTime(Now);
}ASP.NET Coreでは、Program.cs で builder.Services.AddSingleton<IClock, JstClock>(); と登録し、申請・承認の日時や操作ログなど、データベースに残す時刻は DateTime.Now を直接使わずに IClock から取ります。
こうしておくと、試験のときに「今は2026年9月24日の10時」のように日時を固定した時計に差し替えられます。月末・月初の締め処理や、「今月は何月か」で動きが変わる処理を確かめるときに便利です。
なお、.NET 8以降には、「今」を差し替えるための TimeProvider というクラスが標準で入っています。ただ、TimeProvider.System.GetUtcNow() はUTCの時刻を、GetLocalNow() はPCのタイムゾーンでの時刻を返すので、日本時間にするには、こちらでも9時間ずらす必要があります。
注意点
- PCの時計そのものがずれていると、正しい日本時間にはなりません。Windowsの「設定」→「時刻と言語」→「日付と時刻」で、「時刻を自動的に設定する」がオンになっているか確認してください。
- 海外の拠点など、日本以外の時刻も扱うシステムでは、
DateTimeOffsetやTimeZoneInfoを使ってください。 KindがUnspecifiedの値にToUniversalTime()を使うと「PCのローカル時刻」として、ToLocalTime()を使うと「UTC」として扱われます。日本のPCでもToLocalTime()は9時間進んだ時刻になってしまうので、日本時間として持っている値には使わないでください。- 1つのシステムの中で
DateTime.NowとJSTが混ざると、PCのタイムゾーンが日本でないときに、比べると9時間ずれます。時刻を取る場所を1か所(IClock)にまとめておくのが安全です。
最後に
以上が、C#で日本時間(JST)を取得する方法です。
DateTime.UtcNow.AddHours(9) の1行で済みますが、時刻を取る場所を1か所にまとめておくと、サーバの設定で時刻がずれる心配がなくなり、試験もしやすくなります。
この部品を使っている立替精算システムは、こちらで紹介しています。
Pythonでインターネットの時刻サーバ(NTP)から日本時間を取得する方法は、こちらで紹介しています。




コメント