C#で日本時間(JST)を取得する|TimeZoneInfoを使わない理由

C#・.NET

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.NowPCのタイムゾーンでの今サーバの設定で変わる
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)から日本時間を取得する方法は、こちらで紹介しています。

コメント

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