C#の時間処理を完全解説|DateTime・TimeSpan・Stopwatchの使い分けと現在時刻の取得方法

はじめに

C#で時間を扱うとき、多くの人が最初に迷うのが「DateTimeを使えばよいのか」「TimeSpanを使うべきなのか」「処理時間の計測には何を使うのか」という点です。

C#の時間処理では、目的によって使う型が変わります。現在時刻や日付を扱うならDateTimeDateTimeOffset、2つの時刻の差や期間を表すならTimeSpan、プログラムの実行時間や処理速度を測るならStopwatchを使います。

たとえば、ログに現在時刻を出したい場合と、メソッドの実行時間をミリ秒単位で測りたい場合では、適切な型が異なります。どちらも「時間」に関係しますが、C#では同じ方法で扱うべきではありません。

この記事では、C#の時間処理について、DateTimeTimeSpanStopwatchの使い分けを中心に、現在時刻の取得、日付の加算・減算、経過時間の計算、処理時間の計測、文字列フォーマット、タイムゾーンの注意点まで解説します。

1. C#の時間処理で使う主な型と検索ユーザーが迷いやすいポイント

C#で時間を扱うときに主に使う型は、次の4つです。

主な用途
DateTime日付と時刻を表す
DateTimeOffsetタイムゾーンのオフセット付きで日時を表す
TimeSpan時間差・期間・経過時間を表す
Stopwatch処理時間を高精度に計測する

特に初心者が混同しやすいのは、DateTimeTimeSpan、そしてDateTimeStopwatchの違いです。

DateTimeは「2026年6月6日 10時30分」のような特定の日時を表します。一方でTimeSpanは「3時間」「15分」「2日と4時間」のような長さを表します。Stopwatchはプログラムの処理にかかった時間を測るための計測器のようなものです。

1-1. C#で扱う「時刻」「日付」「経過時間」「処理時間」の違い

C#で「時間」と言っても、実際にはいくつかの種類があります。

「時刻」は、ある日の何時何分何秒かを表します。

C#
DateTime now = DateTime.Now;
Console.WriteLine(now); // 例: 2026/06/06 10:30:00

「日付」は、年・月・日を表します。

C#
DateTime today = DateTime.Today;
Console.WriteLine(today); // 例: 2026/06/06 0:00:00

「経過時間」は、開始時刻から終了時刻までの差です。

C#
DateTime start = DateTime.Now;

// 何らかの処理

DateTime end = DateTime.Now;
TimeSpan elapsed = end - start;

「処理時間」は、メソッドやループなどの実行にかかった時間です。処理時間を正確に測る場合はStopwatchを使います。

C#
using System.Diagnostics;

Stopwatch stopwatch = Stopwatch.StartNew();

// 測定したい処理

stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);

1-2. DateTime・TimeSpan・Stopwatchの役割を一覧で比較

DateTimeTimeSpanStopwatchは、それぞれ役割が明確に異なります。

表すもの主な用途
DateTime日付と時刻2026/06/06 10:30:00現在時刻、予約日時、ログ日時
DateTimeOffsetオフセット付き日時2026/06/06 10:30:00 +09:00海外対応、API、分散システム
TimeSpan時間の長さ2時間30分時間差、残り時間、作業時間
Stopwatch計測された処理時間123msパフォーマンス計測、ベンチマーク

同じ「1時間」でも、DateTimeでは「ある日の1時」を意味することがありますが、TimeSpanでは「1時間という長さ」を意味します。この違いを理解することが、C#の時間処理を正しく使う第一歩です。

1-3. どの型を使うべきかを目的別に早見表で整理

目的別に見ると、使うべき型は次のように整理できます。

やりたいこと使う型
現在時刻を取得したいDateTime.NowDateTime.UtcNowDateTimeOffset.Now
今日の日付を取得したいDateTime.Today
2つの日時の差を求めたいTimeSpan
3時間後の日時を求めたいDateTime.AddHours
15分という時間を表したいTimeSpan.FromMinutes
処理時間を測りたいStopwatch
海外ユーザーの時刻を扱いたいDateTimeOffsetTimeZoneInfo
時刻だけを扱いたいTimeOnly
日付だけを扱いたいDateOnly

たとえば「ユーザーが予約した日時」はDateTimeまたはDateTimeOffsetで扱います。一方、「予約時刻まであと何分か」はTimeSpanで扱います。

1-4. 初心者が混同しやすい「現在時刻」と「経過時間」の違い

DateTime.Nowを使えば現在時刻を取得できますが、処理時間の計測に使うのはおすすめできません。

C#
DateTime start = DateTime.Now;

// 処理

DateTime end = DateTime.Now;
TimeSpan elapsed = end - start;

この書き方でも一見動きますが、システム時刻の変更や時刻補正の影響を受ける可能性があります。処理時間を測るなら、次のようにStopwatchを使います。

C#
using System.Diagnostics;

Stopwatch sw = Stopwatch.StartNew();

// 処理

sw.Stop();
Console.WriteLine($"{sw.ElapsedMilliseconds} ms");

「現在が何時か」を知りたいならDateTime、「どれだけ時間が経ったか」を扱うならTimeSpan、「処理に何ミリ秒かかったか」を測るならStopwatchと覚えると迷いにくくなります。

2. C#で現在時刻を取得する方法

C#で現在時刻を取得する代表的な方法は、DateTime.NowDateTime.UtcNowDateTimeOffset.Nowです。

それぞれ似ていますが、ローカル時刻を取得するのか、UTCを取得するのか、タイムゾーン情報を含めるのかが異なります。

2-1. DateTime.Nowでローカルの現在日時を取得する

DateTime.Nowは、実行環境のローカル日時を取得します。

C#
DateTime now = DateTime.Now;
Console.WriteLine(now);

日本のPCや日本時間に設定されたサーバーで実行すると、日本時間の現在日時が取得されます。

C#
DateTime now = DateTime.Now;

Console.WriteLine(now.Year);
Console.WriteLine(now.Month);
Console.WriteLine(now.Day);
Console.WriteLine(now.Hour);
Console.WriteLine(now.Minute);
Console.WriteLine(now.Second);

DateTime.Nowは、画面表示やローカル環境での簡単な時刻取得には便利です。ただし、サーバー環境や海外ユーザー対応では注意が必要です。

2-2. DateTime.UtcNowでUTCの現在日時を取得する

DateTime.UtcNowは、UTCの現在日時を取得します。

C#
DateTime utcNow = DateTime.UtcNow;
Console.WriteLine(utcNow);

UTCは協定世界時で、世界共通の基準となる時刻です。データベースに日時を保存する場合や、複数の国・地域で利用されるシステムでは、ローカル時刻よりUTCで保存する方が安全です。

C#
DateTime createdAt = DateTime.UtcNow;

ログや作成日時を保存するときは、サーバーのタイムゾーンに依存しないUtcNowを使う方が管理しやすくなります。

2-3. DateTimeOffset.Nowでタイムゾーン情報付きの現在時刻を取得する

DateTimeOffset.Nowは、日時に加えてUTCからのオフセットを持ちます。

C#
DateTimeOffset now = DateTimeOffset.Now;
Console.WriteLine(now);

日本時間の場合、+09:00のようなオフセットが含まれます。

C#
DateTimeOffset now = DateTimeOffset.Now;

Console.WriteLine(now.DateTime);
Console.WriteLine(now.Offset);

DateTimeは日時そのものを表しますが、DateTimeOffsetは「UTCから何時間ずれている日時か」も表せます。そのため、APIや海外ユーザー対応、分散システムではDateTimeOffsetの方が適している場合があります。

2-4. 現在の日付だけ・時刻だけを取得する方法

現在の日付だけを取得したい場合は、DateTime.Todayを使います。

C#
DateTime today = DateTime.Today;
Console.WriteLine(today);

DateTime.Todayは、現在日の0時0分0秒を表すDateTimeを返します。

C#
DateTime today = DateTime.Today;
Console.WriteLine(today); // 例: 2026/06/06 0:00:00

現在時刻から日付部分だけを取り出すこともできます。

C#
DateTime dateOnly = DateTime.Now.Date;

時刻部分だけを扱いたい場合、.NET 6以降ではTimeOnlyを使えます。

C#
TimeOnly currentTime = TimeOnly.FromDateTime(DateTime.Now);
Console.WriteLine(currentTime);

日付だけを扱う場合はDateOnlyも使えます。

C#
DateOnly currentDate = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine(currentDate);

2-5. Now・UtcNow・Todayの違いと使い分け

NowUtcNowTodayの違いは次のとおりです。

プロパティ取得できる値用途
DateTime.Nowローカルの現在日時画面表示、ローカル処理
DateTime.UtcNowUTCの現在日時保存、ログ、サーバー処理
DateTime.Todayローカルの今日の日付の0時日付だけ必要な処理

例を見てみましょう。

C#
Console.WriteLine(DateTime.Now);    // ローカル日時
Console.WriteLine(DateTime.UtcNow); // UTC日時
Console.WriteLine(DateTime.Today); // 今日の0時

実務では、内部保存にはUtcNow、ユーザー表示にはローカル時刻、日付判定にはTodayを使うことが多いです。

ただし、システム全体で時刻の扱いを統一しておかないと、比較や検索でバグが起きやすくなります。特にサーバーとクライアントのタイムゾーンが異なる場合は注意が必要です。

3. DateTimeの基本と日付・時刻の操作方法

DateTimeは、C#で日付と時刻を扱うための基本的な型です。現在時刻の取得、日付の作成、加算・減算、比較、文字列変換など、さまざまな場面で使います。

3-1. DateTimeとは何か

DateTimeは、日付と時刻を表す構造体です。

C#
DateTime date = new DateTime(2026, 6, 6, 10, 30, 0);
Console.WriteLine(date);

この例では、2026年6月6日10時30分0秒を表すDateTimeを作成しています。

日付だけを指定することもできます。

C#
DateTime date = new DateTime(2026, 6, 6);
Console.WriteLine(date); // 2026/06/06 0:00:00

DateTimeは、予約日時、作成日時、更新日時、締切日時など、特定の日時を表すときに使います。

3-2. 年・月・日・時・分・秒を取得する方法

DateTimeから年、月、日、時、分、秒を取得するには、各プロパティを使います。

C#
DateTime now = DateTime.Now;

int year = now.Year;
int month = now.Month;
int day = now.Day;
int hour = now.Hour;
int minute = now.Minute;
int second = now.Second;
int millisecond = now.Millisecond;

Console.WriteLine(year);
Console.WriteLine(month);
Console.WriteLine(day);
Console.WriteLine(hour);
Console.WriteLine(minute);
Console.WriteLine(second);
Console.WriteLine(millisecond);

曜日を取得する場合はDayOfWeekを使います。

C#
DateTime now = DateTime.Now;
DayOfWeek dayOfWeek = now.DayOfWeek;

Console.WriteLine(dayOfWeek);

年間の何日目かを取得したい場合はDayOfYearを使います。

C#
Console.WriteLine(DateTime.Now.DayOfYear);

3-3. 日付や時刻を加算・減算する方法

DateTimeの日付や時刻を加算・減算するには、AddYearsAddMonthsAddDaysAddHoursAddMinutesAddSecondsなどを使います。

C#
DateTime now = DateTime.Now;

DateTime tomorrow = now.AddDays(1);
DateTime nextWeek = now.AddDays(7);
DateTime afterOneHour = now.AddHours(1);
DateTime beforeThirtyMinutes = now.AddMinutes(-30);

Console.WriteLine(tomorrow);
Console.WriteLine(nextWeek);
Console.WriteLine(afterOneHour);
Console.WriteLine(beforeThirtyMinutes);

DateTimeは不変の値です。AddDaysを呼び出しても元の値は変わらず、新しいDateTimeが返されます。

C#
DateTime now = DateTime.Now;
now.AddDays(1);

Console.WriteLine(now); // 元の日時のまま

正しく使うには、戻り値を変数に代入します。

C#
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1);

3-4. 2つのDateTimeの差分を求める方法

2つのDateTimeの差分を求めると、TimeSpanが得られます。

C#
DateTime start = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime end = new DateTime(2026, 6, 6, 17, 30, 0);

TimeSpan workTime = end - start;

Console.WriteLine(workTime); // 08:30:00
Console.WriteLine(workTime.TotalHours); // 8.5

Subtractメソッドを使っても同じです。

C#
TimeSpan diff = end.Subtract(start);

差分を求めるときは、開始時刻と終了時刻の順序に注意します。

C#
TimeSpan diff1 = end - start; // 正の値
TimeSpan diff2 = start - end; // 負の値

3-5. 日付の比較と並び替えを行う方法

DateTimeは比較演算子で大小比較できます。

C#
DateTime deadline = new DateTime(2026, 6, 30);
DateTime now = DateTime.Now;

if (now > deadline)
{
Console.WriteLine("期限を過ぎています");
}
else
{
Console.WriteLine("期限内です");
}

等しいかどうかを比較することもできます。

C#
DateTime date1 = new DateTime(2026, 6, 6);
DateTime date2 = new DateTime(2026, 6, 6);

Console.WriteLine(date1 == date2); // True

リストを日付順に並び替える場合は、OrderByを使います。

C#
var dates = new List<DateTime>
{
new DateTime(2026, 6, 10),
new DateTime(2026, 6, 1),
new DateTime(2026, 6, 5)
};

var sortedDates = dates.OrderBy(d => d).ToList();

foreach (var date in sortedDates)
{
Console.WriteLine(date);
}

降順にする場合はOrderByDescendingを使います。

C#
var sortedDesc = dates.OrderByDescending(d => d).ToList();

3-6. DateTimeKindとローカル時刻・UTCの扱い

DateTimeには、Kindというプロパティがあります。これは、その日時がローカル時刻なのかUTCなのか、未指定なのかを表します。

C#
DateTime local = DateTime.Now;
DateTime utc = DateTime.UtcNow;
DateTime unspecified = new DateTime(2026, 6, 6, 10, 0, 0);

Console.WriteLine(local.Kind); // Local
Console.WriteLine(utc.Kind); // Utc
Console.WriteLine(unspecified.Kind); // Unspecified

DateTimeKindには次の3種類があります。

意味
Localローカル時刻
UtcUTC
Unspecifiedどちらか未指定

ローカル時刻をUTCに変換するにはToUniversalTimeを使います。

C#
DateTime localNow = DateTime.Now;
DateTime utcNow = localNow.ToUniversalTime();

UTCをローカル時刻に変換するにはToLocalTimeを使います。

C#
DateTime utc = DateTime.UtcNow;
DateTime local = utc.ToLocalTime();

日時を比較するときにKindが混在していると、意図しない結果になることがあります。実務では、保存時はUTC、表示時はローカル時刻に変換する方針にすると管理しやすくなります。

4. TimeSpanの基本と時間差・期間の扱い方

TimeSpanは、時間の長さや期間を表す型です。DateTimeが「いつ」を表すのに対して、TimeSpanは「どれくらい」を表します。

4-1. TimeSpanとは何か

TimeSpanは、日数、時間、分、秒、ミリ秒などの時間間隔を表します。

C#
TimeSpan duration = new TimeSpan(1, 30, 0);
Console.WriteLine(duration); // 01:30:00

この例では、1時間30分を表しています。

日数を含める場合は、次のように書けます。

C#
TimeSpan duration = new TimeSpan(2, 3, 30, 0);
Console.WriteLine(duration); // 2.03:30:00

これは、2日と3時間30分を表します。

TimeSpanは、作業時間、残り時間、待機時間、経過時間、タイマー処理などでよく使います。

4-2. 時間・分・秒・ミリ秒を表すTimeSpanの作り方

TimeSpanを作る方法はいくつかあります。

コンストラクタを使う方法です。

C#
TimeSpan time1 = new TimeSpan(1, 30, 0); // 1時間30分
TimeSpan time2 = new TimeSpan(2, 1, 30, 0); // 2日1時間30分

FromHoursFromMinutesFromSecondsなどのメソッドを使う方法もあります。

C#
TimeSpan oneHour = TimeSpan.FromHours(1);
TimeSpan thirtyMinutes = TimeSpan.FromMinutes(30);
TimeSpan tenSeconds = TimeSpan.FromSeconds(10);
TimeSpan fiveHundredMilliseconds = TimeSpan.FromMilliseconds(500);

From系メソッドは、何の単位で作っているかが分かりやすいため、実務ではよく使われます。

C#
TimeSpan timeout = TimeSpan.FromSeconds(30);
TimeSpan interval = TimeSpan.FromMinutes(5);

4-3. TotalHours・TotalMinutes・TotalSecondsとHours・Minutes・Secondsの違い

TimeSpanで特に間違いやすいのが、TotalHoursHoursの違いです。

C#
TimeSpan span = TimeSpan.FromMinutes(90);

Console.WriteLine(span.TotalHours); // 1.5
Console.WriteLine(span.Hours); // 1
Console.WriteLine(span.Minutes); // 30
Console.WriteLine(span.TotalMinutes); // 90

TotalHoursは、全体を時間単位に換算した値です。90分なら1.5時間です。

一方、Hoursは「日数を除いた時間部分」です。90分の場合は1時間30分なので、Hoursは1、Minutesは30になります。

24時間を超える場合はさらに注意が必要です。

C#
TimeSpan span = TimeSpan.FromHours(27);

Console.WriteLine(span.TotalHours); // 27
Console.WriteLine(span.Days); // 1
Console.WriteLine(span.Hours); // 3

合計時間が欲しい場合はTotalHours、表示用に時間部分だけ取り出したい場合はHoursを使います。

4-4. TimeSpanで経過時間や残り時間を計算する方法

開始日時と終了日時の差を求めると、TimeSpanになります。

C#
DateTime start = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime end = new DateTime(2026, 6, 6, 18, 0, 0);

TimeSpan elapsed = end - start;

Console.WriteLine(elapsed.TotalHours); // 9

指定日時までの残り時間を計算する場合は、目標日時から現在時刻を引きます。

C#
DateTime target = new DateTime(2026, 12, 31, 23, 59, 59);
TimeSpan remaining = target - DateTime.Now;

Console.WriteLine($"残り {remaining.Days} 日");

残り時間がマイナスになる可能性がある場合は、判定を入れると安全です。

C#
if (remaining.TotalSeconds > 0)
{
Console.WriteLine($"残り {remaining.TotalMinutes} 分");
}
else
{
Console.WriteLine("期限を過ぎています");
}

4-5. TimeSpanを使うべきケースと使わない方がよいケース

TimeSpanを使うべきケースは、時間の長さを扱う場合です。

C#
TimeSpan restTime = TimeSpan.FromMinutes(10);
TimeSpan workTime = TimeSpan.FromHours(8);
TimeSpan timeout = TimeSpan.FromSeconds(30);

次のようなケースではTimeSpanが適しています。

ケース
作業時間8時間30分
残り時間締切まであと3日
待機時間5秒待つ
タイムアウト30秒で中断
経過時間開始から15分

一方で、特定の日時を表したい場合はTimeSpanではなくDateTimeを使います。

C#
// 悪い例: 予約日時をTimeSpanで表そうとする
TimeSpan reservationTime = TimeSpan.FromHours(10);

10時の予約を表したいなら、日付も含めてDateTimeで表します。

C#
DateTime reservationDateTime = new DateTime(2026, 6, 6, 10, 0, 0);

時刻だけを扱いたい場合は、.NET 6以降であればTimeOnlyも選択肢になります。

5. Stopwatchで処理時間を正確に計測する方法

プログラムの処理時間を測る場合は、DateTime.NowではなくStopwatchを使います。Stopwatchは高精度のタイマーを利用して、処理にかかった時間を計測するための型です。

5-1. Stopwatchとは何か

Stopwatchは、System.Diagnostics名前空間にあるクラスです。

C#
using System.Diagnostics;

処理時間を測る基本的なコードは次のとおりです。

C#
using System.Diagnostics;

Stopwatch stopwatch = new Stopwatch();

stopwatch.Start();

// 測定したい処理
Thread.Sleep(1000);

stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Thread.Sleep(1000)は約1秒待機する処理です。この例では、約1000ミリ秒前後の値が表示されます。

5-2. Start・Stop・Restart・Resetの基本的な使い方

Stopwatchには、計測を制御するためのメソッドがあります。

メソッド意味
Start計測を開始または再開する
Stop計測を停止する
Restart経過時間をリセットして再計測する
Reset経過時間をリセットする

基本的な使い方です。

C#
Stopwatch sw = new Stopwatch();

sw.Start();
// 処理A
sw.Stop();

Console.WriteLine(sw.ElapsedMilliseconds);

StartNewを使うと、インスタンス作成と開始を同時に行えます。

C#
Stopwatch sw = Stopwatch.StartNew();

// 測定したい処理

sw.Stop();
Console.WriteLine(sw.Elapsed);

同じStopwatchで再度測り直したい場合はRestartを使います。

C#
sw.Restart();

// 別の処理

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

Resetは、経過時間をゼロに戻しますが、計測は開始しません。

C#
sw.Reset();

5-3. Elapsed・ElapsedMilliseconds・ElapsedTicksの違い

Stopwatchの計測結果は、主に次のプロパティで取得します。

プロパティ戻り値用途
ElapsedTimeSpan日時形式で経過時間を扱う
ElapsedMillisecondslongミリ秒単位で取得する
ElapsedTickslongタイマーのtick単位で取得する

例を見てみましょう。

C#
Stopwatch sw = Stopwatch.StartNew();

Thread.Sleep(1234);

sw.Stop();

Console.WriteLine(sw.Elapsed);
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(sw.ElapsedTicks);

表示や計算にはElapsedMillisecondsが使いやすいです。より細かい単位で分析したい場合はElapsedTicksを使うこともあります。

ElapsedTimeSpanなので、次のように合計秒数を取得できます。

C#
Console.WriteLine(sw.Elapsed.TotalSeconds);

5-4. DateTimeではなくStopwatchで計測すべき理由

処理時間の計測にDateTime.Nowを使う例もあります。

C#
DateTime start = DateTime.Now;

// 処理

DateTime end = DateTime.Now;
TimeSpan elapsed = end - start;

しかし、処理時間を測る目的ではStopwatchの方が適しています。

理由は、DateTime.Nowがシステム時刻を基準にしているためです。システム時刻が変更されたり、時刻同期によって補正されたりすると、計測結果に影響が出る可能性があります。

一方、Stopwatchは経過時間の計測に向いた仕組みを使います。パフォーマンス計測やベンチマークでは、基本的にStopwatchを使います。

C#
Stopwatch sw = Stopwatch.StartNew();

// 測定したい処理

sw.Stop();

Console.WriteLine($"{sw.ElapsedMilliseconds} ms");

5-5. パフォーマンス計測で注意すべきポイント

Stopwatchを使っても、計測結果は毎回完全に同じになるとは限りません。OSの状態、CPU負荷、メモリ使用量、JITコンパイル、GCなどの影響を受けるためです。

簡単な計測では、複数回実行して平均を見るとよいです。

C#
for (int i = 0; i < 5; i++)
{
Stopwatch sw = Stopwatch.StartNew();

// 測定したい処理
Thread.Sleep(100);

sw.Stop();

Console.WriteLine($"{i + 1}回目: {sw.ElapsedMilliseconds} ms");
}

初回だけ遅くなることもあるため、厳密なベンチマークではウォームアップを行います。

C#
// ウォームアップ
TargetMethod();

Stopwatch sw = Stopwatch.StartNew();

TargetMethod();

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

また、非常に短い処理は1回だけ測ると誤差が大きくなります。その場合は、何度も繰り返して合計時間を測る方法が有効です。

C#
Stopwatch sw = Stopwatch.StartNew();

for (int i = 0; i < 100000; i++)
{
TargetMethod();
}

sw.Stop();

Console.WriteLine(sw.ElapsedMilliseconds);

6. DateTime・TimeSpan・Stopwatchの使い分け

C#の時間処理では、目的に応じて型を選ぶことが重要です。なんでもDateTimeで扱おうとすると、処理時間の計測や期間の表現で分かりにくいコードになってしまいます。

6-1. 現在時刻を取得したい場合はDateTimeまたはDateTimeOffset

現在時刻を取得したい場合は、DateTimeまたはDateTimeOffsetを使います。

C#
DateTime now = DateTime.Now;
DateTime utcNow = DateTime.UtcNow;
DateTimeOffset offsetNow = DateTimeOffset.Now;

画面に現在時刻を表示するだけなら、DateTime.Nowで十分なこともあります。

C#
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));

データベースに保存する作成日時や更新日時なら、DateTime.UtcNowを使う方が扱いやすいです。

C#
DateTime createdAt = DateTime.UtcNow;

タイムゾーンのオフセットも含めて扱いたい場合は、DateTimeOffsetを検討します。

C#
DateTimeOffset createdAt = DateTimeOffset.UtcNow;

6-2. 日付や時刻の差分を扱いたい場合はTimeSpan

2つの日時の差や、時間の長さを表す場合はTimeSpanを使います。

C#
DateTime start = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime end = new DateTime(2026, 6, 6, 17, 45, 0);

TimeSpan workTime = end - start;

Console.WriteLine(workTime.TotalHours);

タイマーや待機時間のように、特定の日時ではなく時間の長さだけが必要な場合にもTimeSpanが適しています。

C#
TimeSpan interval = TimeSpan.FromMinutes(10);
TimeSpan timeout = TimeSpan.FromSeconds(30);

6-3. 処理速度や実行時間を測りたい場合はStopwatch

メソッドの実行時間、ループ処理の速度、API呼び出しにかかった時間などを測る場合はStopwatchを使います。

C#
Stopwatch sw = Stopwatch.StartNew();

DoSomething();

sw.Stop();

Console.WriteLine($"処理時間: {sw.ElapsedMilliseconds} ms");

処理時間の計測にDateTime.Nowを使うと、システム時刻の変更などの影響を受ける可能性があります。パフォーマンス計測ではStopwatchを選ぶのが基本です。

6-4. 予約・勤怠・ログ・タイマー・ベンチマークでの使い分け例

実務での使い分けを例で整理します。

用途使う型理由
予約日時DateTimeDateTimeOffset特定の日時を表すため
勤怠の出勤時刻DateTimeいつ出勤したかを記録するため
勤務時間TimeSpan出勤から退勤までの長さを表すため
ログの記録時刻DateTime.UtcNow保存時刻を統一しやすいため
タイマーの間隔TimeSpan5分ごと、30秒ごとなどの期間を表すため
ベンチマークStopwatch処理時間を正確に測るため

たとえば勤怠処理では、出勤時刻と退勤時刻はDateTime、勤務時間は差分としてTimeSpanで表します。

C#
DateTime clockIn = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime clockOut = new DateTime(2026, 6, 6, 18, 0, 0);

TimeSpan workTime = clockOut - clockIn;

Console.WriteLine($"勤務時間: {workTime.TotalHours}時間");

6-5. 実務で迷わないための判断フロー

C#の時間処理で迷ったら、次の順番で考えると判断しやすくなります。

まず、「特定の日時」を扱いたいかを考えます。たとえば「2026年6月6日10時」のような値ならDateTimeまたはDateTimeOffsetです。

次に、「時間の長さ」を扱いたいかを考えます。たとえば「30分」「2時間」「3日間」ならTimeSpanです。

最後に、「処理にかかった時間」を測りたいかを考えます。たとえば「このメソッドは何ミリ秒で終わるか」を知りたいならStopwatchです。

判断をコードの例にすると、次のようになります。

C#
// 現在時刻
DateTime now = DateTime.Now;

// 時間の長さ
TimeSpan duration = TimeSpan.FromMinutes(30);

// 処理時間の計測
Stopwatch sw = Stopwatch.StartNew();

「いつ」を表すならDateTime、「どれくらい」を表すならTimeSpan、「どれだけ速いか」を測るならStopwatchと覚えておくと、実務でも迷いにくくなります。

7. C#で日付・時刻を文字列に変換・表示する方法

日付や時刻は、そのまま表示すると環境によって形式が変わることがあります。そのため、ユーザーに見せる文字列やログ出力では、ToStringで明示的にフォーマットすることが重要です。

7-1. ToStringで日付や時刻をフォーマットする

DateTimeToStringで文字列に変換できます。

C#
DateTime now = DateTime.Now;

Console.WriteLine(now.ToString());

フォーマットを指定すると、表示形式を制御できます。

C#
Console.WriteLine(now.ToString("yyyy/MM/dd"));
Console.WriteLine(now.ToString("HH:mm:ss"));
Console.WriteLine(now.ToString("yyyy/MM/dd HH:mm:ss"));

主なフォーマット指定子は次のとおりです。

指定子意味
yyyy年4桁2026
MM月2桁06
dd日2桁06
HH時2桁、24時間表記09
mm分2桁30
ss秒2桁05
fffミリ秒3桁123

MMは月、mmは分です。この違いは非常によく間違えます。

7-2. yyyy/MM/dd HH:mm:ss形式で表示する

ログや画面表示でよく使う形式が、yyyy/MM/dd HH:mm:ssです。

C#
DateTime now = DateTime.Now;

string text = now.ToString("yyyy/MM/dd HH:mm:ss");

Console.WriteLine(text);

ハイフン区切りにしたい場合は、次のように書きます。

C#
string text = now.ToString("yyyy-MM-dd HH:mm:ss");

ミリ秒まで表示したい場合は、fffを追加します。

C#
string text = now.ToString("yyyy/MM/dd HH:mm:ss.fff");

ファイル名に日時を使う場合は、スラッシュやコロンを避ける必要があります。

C#
string fileName = DateTime.Now.ToString("yyyyMMdd_HHmmss");
Console.WriteLine(fileName);

7-3. 日本語環境で曜日や年月日を表示する

日本語の年月日形式で表示するには、次のようにフォーマットします。

C#
DateTime now = DateTime.Now;

Console.WriteLine(now.ToString("yyyy年MM月dd日"));

曜日を表示するにはdddまたはddddを使います。

C#
Console.WriteLine(now.ToString("yyyy年MM月dd日 ddd"));
Console.WriteLine(now.ToString("yyyy年MM月dd日 dddd"));

日本語の曜日名を確実に表示したい場合は、CultureInfoを指定します。

C#
using System.Globalization;

DateTime now = DateTime.Now;
CultureInfo culture = new CultureInfo("ja-JP");

string text = now.ToString("yyyy年MM月dd日 dddd", culture);

Console.WriteLine(text);

曜日を「月」「火」のように表示したい場合は、dddを使います。

C#
string text = now.ToString("yyyy年MM月dd日(ddd)", new CultureInfo("ja-JP"));
Console.WriteLine(text);

7-4. 文字列からDateTimeに変換するParse・TryParse

文字列をDateTimeに変換するには、ParseまたはTryParseを使います。

C#
string text = "2026/06/06 10:30:00";

DateTime date = DateTime.Parse(text);

Console.WriteLine(date);

ただし、Parseは変換できない文字列が渡されると例外が発生します。

C#
string text = "不正な日付";

DateTime date = DateTime.Parse(text); // 例外

ユーザー入力や外部データを扱う場合は、TryParseを使う方が安全です。

C#
string text = "2026/06/06 10:30:00";

if (DateTime.TryParse(text, out DateTime date))
{
Console.WriteLine(date);
}
else
{
Console.WriteLine("日付に変換できません");
}

形式が決まっている文字列を変換する場合は、ParseExactまたはTryParseExactを使います。

C#
using System.Globalization;

string text = "20260606";
DateTime date = DateTime.ParseExact(
text,
"yyyyMMdd",
CultureInfo.InvariantCulture
);

Console.WriteLine(date);

安全に変換するならTryParseExactです。

C#
string text = "20260606";

bool success = DateTime.TryParseExact(
text,
"yyyyMMdd",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out DateTime date
);

if (success)
{
Console.WriteLine(date);
}

7-5. フォーマット変換でよくあるエラーと対処法

日付フォーマットでよくあるミスの1つが、月と分の指定子を間違えることです。

C#
DateTime now = DateTime.Now;

// 悪い例
Console.WriteLine(now.ToString("yyyy/mm/dd HH:MM:ss"));

MMは月、mmは分です。正しくは次のように書きます。

C#
Console.WriteLine(now.ToString("yyyy/MM/dd HH:mm:ss"));

また、文字列の日付形式とフォーマット指定が一致していないと、ParseExactでエラーになります。

C#
string text = "2026/06/06";

// フォーマットが一致していない
DateTime date = DateTime.ParseExact(
text,
"yyyyMMdd",
CultureInfo.InvariantCulture
);

正しくは、文字列に合わせて指定します。

C#
DateTime date = DateTime.ParseExact(
text,
"yyyy/MM/dd",
CultureInfo.InvariantCulture
);

ユーザー入力を処理するときは、例外を避けるためにTryParseTryParseExactを使うのがおすすめです。

8. タイムゾーン・UTC・DateTimeOffsetの注意点

C#の時間処理で実務上よく問題になるのが、タイムゾーンとUTCの扱いです。ローカル環境では問題なく動いていても、サーバー環境や海外ユーザー対応でバグになることがあります。

8-1. ローカル時刻とUTCの違い

ローカル時刻は、実行環境のタイムゾーンに基づく時刻です。日本の環境なら日本時間、UTC設定のサーバーならUTCがローカル時刻になります。

C#
DateTime localNow = DateTime.Now;
DateTime utcNow = DateTime.UtcNow;

Console.WriteLine(localNow);
Console.WriteLine(utcNow);

UTCは世界共通の基準時刻です。日本時間は通常UTCより9時間進んでいます。

ローカル時刻はユーザーに表示するには便利ですが、保存や比較では環境差の影響を受けることがあります。そのため、システム内部ではUTCで扱い、表示するときにローカル時刻へ変換する設計がよく使われます。

8-2. サーバー環境でDateTime.Nowを使うリスク

サーバーでDateTime.Nowを使うと、サーバーのタイムゾーン設定に依存します。

C#
DateTime now = DateTime.Now;

開発環境では日本時間だったのに、本番サーバーではUTCだったというケースでは、保存される時刻がずれる可能性があります。

ログ、作成日時、更新日時など、システム内部で保存する時刻はDateTime.UtcNowを使う方が安全です。

C#
DateTime createdAt = DateTime.UtcNow;
DateTime updatedAt = DateTime.UtcNow;

画面に表示するときに、必要に応じてユーザーのタイムゾーンへ変換します。

C#
DateTime localTime = createdAt.ToLocalTime();

ただし、ToLocalTimeは実行環境のローカルタイムゾーンに変換するため、ユーザーごとのタイムゾーン変換にはTimeZoneInfoを使います。

8-3. 海外ユーザー対応ではDateTimeOffsetを検討する

海外ユーザーがいるシステムや、APIで日時をやり取りするシステムでは、DateTimeOffsetが役立ちます。

C#
DateTimeOffset now = DateTimeOffset.Now;
Console.WriteLine(now);

DateTimeOffsetは、日時とUTCからのオフセットを一緒に保持します。

C#
DateTimeOffset tokyoTime = new DateTimeOffset(
2026, 6, 6, 10, 0, 0,
TimeSpan.FromHours(9)
);

Console.WriteLine(tokyoTime);

DateTimeだけでは、その日時がどのタイムゾーンのものか分かりにくい場合があります。DateTimeOffsetを使うと、+09:00のようなオフセットを含めて扱えるため、日時の意味が明確になります。

APIのレスポンスやイベント日時など、外部システムと連携する値ではDateTimeOffsetを使う設計も検討するとよいです。

8-4. UTCで保存して表示時にローカル変換する基本方針

実務では、次の方針がよく使われます。

処理方針
データ保存UTCで保存
日時比較UTC同士で比較
ユーザー表示ユーザーのタイムゾーンに変換
API連携UTCまたはオフセット付き日時を使う

作成日時を保存する例です。

C#
DateTime createdAtUtc = DateTime.UtcNow;

日本時間で表示する例です。

C#
TimeZoneInfo tokyoTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");

DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(
createdAtUtc,
tokyoTimeZone
);

Console.WriteLine(tokyoTime);

UTCで保存しておけば、サーバーの設置場所やタイムゾーン設定が変わっても、日時の基準を統一しやすくなります。

8-5. タイムゾーン変換で使うTimeZoneInfoの基本

TimeZoneInfoを使うと、指定したタイムゾーンに日時を変換できます。

C#
DateTime utcNow = DateTime.UtcNow;

TimeZoneInfo tokyoTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");

DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, tokyoTimeZone);

Console.WriteLine(tokyoTime);

UTCから別のタイムゾーンに変換する場合は、ConvertTimeFromUtcを使います。

C#
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, tokyoTimeZone);

あるタイムゾーンの時刻をUTCに変換する場合は、ConvertTimeToUtcを使います。

C#
DateTime tokyoDateTime = new DateTime(2026, 6, 6, 10, 0, 0);

DateTime utcDateTime = TimeZoneInfo.ConvertTimeToUtc(
tokyoDateTime,
tokyoTimeZone
);

Console.WriteLine(utcDateTime);

タイムゾーンIDは環境によって扱いが異なる場合があります。Windows環境では日本時間に"Tokyo Standard Time"を使うことが一般的です。

9. C#の時間処理でよくある実装例

ここでは、C#の時間処理でよく使う実装例を紹介します。現在時刻のログ出力、残り時間の計算、作業時間の計算、一定間隔の処理、メソッドの実行時間計測など、実務で使いやすい形で見ていきます。

9-1. 現在時刻をログに出力する

ログに現在時刻を出力する場合は、形式を明示しておくと読みやすくなります。

C#
DateTime now = DateTime.Now;

Console.WriteLine($"[{now:yyyy/MM/dd HH:mm:ss}] 処理を開始しました");

UTCでログを残す場合は、DateTime.UtcNowを使います。

C#
DateTime utcNow = DateTime.UtcNow;

Console.WriteLine($"[{utcNow:yyyy-MM-dd HH:mm:ss} UTC] 処理を開始しました");

ミリ秒まで出力したい場合は、fffを使います。

C#
Console.WriteLine($"[{DateTime.Now:yyyy/MM/dd HH:mm:ss.fff}] 処理を開始しました");

ログは後から調査するための情報なので、時刻形式を統一しておくことが大切です。

9-2. 指定日時までの残り時間を表示する

指定日時までの残り時間は、目標日時から現在時刻を引いて求めます。

C#
DateTime target = new DateTime(2026, 12, 31, 23, 59, 59);
TimeSpan remaining = target - DateTime.Now;

if (remaining.TotalSeconds > 0)
{
Console.WriteLine($"残り {remaining.Days}日 {remaining.Hours}時間 {remaining.Minutes}分");
}
else
{
Console.WriteLine("指定日時を過ぎています");
}

remaining.Daysremaining.Hoursremaining.Minutesは、それぞれ日、時間部分、分部分です。

合計時間で表示したい場合はTotalHoursを使います。

C#
Console.WriteLine($"残り約 {remaining.TotalHours:F1} 時間");

9-3. 開始時刻と終了時刻から作業時間を計算する

勤怠や作業記録では、開始時刻と終了時刻の差から作業時間を求めます。

C#
DateTime start = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime end = new DateTime(2026, 6, 6, 18, 30, 0);

TimeSpan workTime = end - start;

Console.WriteLine($"作業時間: {workTime.TotalHours}時間");

休憩時間を差し引く場合は、TimeSpanを引き算します。

C#
TimeSpan breakTime = TimeSpan.FromHours(1);
TimeSpan actualWorkTime = workTime - breakTime;

Console.WriteLine($"実作業時間: {actualWorkTime.TotalHours}時間");

分単位で表示したい場合はTotalMinutesを使います。

C#
Console.WriteLine($"実作業時間: {actualWorkTime.TotalMinutes}分");

9-4. 一定時間ごとの処理に使う

一定時間ごとに処理を行う場合は、TimerTask.DelayTimeSpanを組み合わせます。

非同期処理で一定時間待つ例です。

C#
await Task.Delay(TimeSpan.FromSeconds(5));
Console.WriteLine("5秒後に実行されました");

ループで一定間隔ごとに処理する例です。

C#
while (true)
{
Console.WriteLine($"実行時刻: {DateTime.Now:HH:mm:ss}");

await Task.Delay(TimeSpan.FromMinutes(1));
}

タイムアウト値をTimeSpanで定義すると、コードの意味が分かりやすくなります。

C#
TimeSpan timeout = TimeSpan.FromSeconds(30);

数値だけで30000と書くよりも、TimeSpan.FromSeconds(30)の方が意図が明確です。

9-5. メソッドの実行時間を計測する

メソッドの実行時間を測るにはStopwatchを使います。

C#
using System.Diagnostics;

Stopwatch sw = Stopwatch.StartNew();

DoSomething();

sw.Stop();

Console.WriteLine($"実行時間: {sw.ElapsedMilliseconds} ms");

複数回測って平均を出す例です。

C#
long totalMilliseconds = 0;

for (int i = 0; i < 10; i++)
{
Stopwatch sw = Stopwatch.StartNew();

DoSomething();

sw.Stop();

totalMilliseconds += sw.ElapsedMilliseconds;
}

Console.WriteLine($"平均実行時間: {totalMilliseconds / 10.0} ms");

処理が短すぎる場合は、繰り返し回数を増やして計測すると誤差を抑えやすくなります。

9-6. ミリ秒単位・秒単位で時間を扱う

ミリ秒単位の時間を扱う場合は、TimeSpan.FromMillisecondsを使います。

C#
TimeSpan interval = TimeSpan.FromMilliseconds(500);
Console.WriteLine(interval.TotalSeconds); // 0.5

秒単位ならTimeSpan.FromSecondsです。

C#
TimeSpan timeout = TimeSpan.FromSeconds(10);
Console.WriteLine(timeout.TotalMilliseconds); // 10000

Stopwatchでミリ秒を取得する場合は、ElapsedMillisecondsを使います。

C#
Stopwatch sw = Stopwatch.StartNew();

Thread.Sleep(500);

sw.Stop();

Console.WriteLine(sw.ElapsedMilliseconds);

秒数として表示したい場合は、Elapsed.TotalSecondsを使います。

C#
Console.WriteLine(sw.Elapsed.TotalSeconds);

用途によって、TimeSpanTotalMillisecondsTotalSecondsStopwatchElapsedMillisecondsを使い分けましょう。

10. C#の時間処理でよくある疑問とトラブル対策

C#の時間処理では、DateTime.NowDateTime.UtcNowの違い、TimeSpanのプロパティの違い、Stopwatchの計測結果のばらつき、DateTimeKindの混在などでトラブルが起きやすいです。

10-1. DateTime.NowとDateTime.UtcNowはどちらを使うべきか

画面に現在時刻を表示するだけなら、DateTime.Nowで問題ないことが多いです。

C#
Console.WriteLine(DateTime.Now);

一方、データベースに保存する作成日時や更新日時、ログ、サーバー間で共有する日時にはDateTime.UtcNowが向いています。

C#
DateTime createdAt = DateTime.UtcNow;

判断基準は次のとおりです。

用途推奨
ユーザーへのローカル表示DateTime.Nowまたは変換後のローカル時刻
DB保存DateTime.UtcNow
ログDateTime.UtcNow
API連携UTCまたはDateTimeOffset
海外対応DateTimeOffsetTimeZoneInfo

迷った場合、保存はUTC、表示はローカル変換という方針にすると安全です。

10-2. TimeSpanで24時間を超える時間を扱うときの注意点

TimeSpanで24時間を超える時間を扱うときは、HoursTotalHoursの違いに注意します。

C#
TimeSpan span = TimeSpan.FromHours(30);

Console.WriteLine(span.Days); // 1
Console.WriteLine(span.Hours); // 6
Console.WriteLine(span.TotalHours); // 30

Hoursは時間部分だけを返します。30時間は1日と6時間なので、Hoursは6です。

合計時間を取得したい場合は、必ずTotalHoursを使います。

C#
double totalHours = span.TotalHours;

同じように、合計分ならTotalMinutes、合計秒ならTotalSecondsを使います。

C#
Console.WriteLine(span.TotalMinutes);
Console.WriteLine(span.TotalSeconds);

表示用にはDaysHoursMinutesを組み合わせ、計算用にはTotal系を使うと分かりやすくなります。

10-3. Stopwatchの計測結果が毎回変わる理由

Stopwatchで同じ処理を測っても、毎回まったく同じ結果になるとは限りません。

理由として、次のような要素があります。

要素影響
CPU負荷他のプロセスの影響を受ける
JITコンパイル初回実行が遅くなることがある
GCガベージコレクションで一時的に遅くなる
I/Oファイルやネットワークはばらつきやすい
キャッシュ2回目以降の方が速くなることがある

そのため、1回だけの結果で判断するのではなく、複数回測ることが大切です。

C#
for (int i = 0; i < 10; i++)
{
Stopwatch sw = Stopwatch.StartNew();

DoSomething();

sw.Stop();

Console.WriteLine(sw.ElapsedMilliseconds);
}

厳密なベンチマークでは、専用のベンチマークライブラリを使うことも検討します。

10-4. DateTimeの比較結果がおかしいときの確認ポイント

DateTimeの比較結果がおかしい場合は、まずKindを確認します。

C#
DateTime date1 = DateTime.Now;
DateTime date2 = DateTime.UtcNow;

Console.WriteLine(date1.Kind);
Console.WriteLine(date2.Kind);

LocalUtcが混在していると、意図しない比較になることがあります。

比較する前に、同じ基準にそろえることが大切です。

C#
DateTime local = DateTime.Now;
DateTime utc = local.ToUniversalTime();

日付だけを比較したいのに、時刻部分まで比較してしまうケースもよくあります。

C#
DateTime date1 = new DateTime(2026, 6, 6, 9, 0, 0);
DateTime date2 = new DateTime(2026, 6, 6, 18, 0, 0);

Console.WriteLine(date1 == date2); // False

日付だけを比較したい場合は、Dateプロパティを使います。

C#
Console.WriteLine(date1.Date == date2.Date); // True

10-5. 時刻だけを扱いたい場合はTimeOnlyを使えるか

.NET 6以降では、時刻だけを表すTimeOnlyを使えます。

C#
TimeOnly time = new TimeOnly(10, 30, 0);
Console.WriteLine(time);

DateTimeで時刻だけを扱おうとすると、不要な日付部分が付いてきます。

C#
DateTime time = new DateTime(1, 1, 1, 10, 30, 0);

このような場合、TimeOnlyを使うと意図が明確になります。

C#
TimeOnly startTime = new TimeOnly(9, 0);
TimeOnly endTime = new TimeOnly(18, 0);

TimeSpan workTime = endTime - startTime;

Console.WriteLine(workTime);

日付だけを扱いたい場合はDateOnlyを使えます。

C#
DateOnly date = new DateOnly(2026, 6, 6);
Console.WriteLine(date);

ただし、既存プロジェクトや古い.NET環境ではDateOnlyTimeOnlyが使えない場合があります。その場合はDateTimeを使い、日付だけならDate、時刻の長さならTimeSpanで扱います。

まとめ

C#の時間処理では、目的に合わせて適切な型を選ぶことが重要です。

現在時刻や予約日時、ログ日時のように「特定の日時」を扱う場合はDateTimeDateTimeOffsetを使います。現在時刻を取得するにはDateTime.Now、UTCの現在時刻を取得するにはDateTime.UtcNow、オフセット付きの日時を扱うにはDateTimeOffset.Nowが使えます。

時間差や期間を表す場合はTimeSpanを使います。たとえば、開始時刻と終了時刻の差、締切までの残り時間、作業時間、タイムアウト時間などはTimeSpanで表すと分かりやすくなります。HoursTotalHoursMinutesTotalMinutesの違いには注意が必要です。

処理時間や実行速度を測る場合はStopwatchを使います。DateTime.Nowでも差分は求められますが、パフォーマンス計測には適していません。メソッドの実行時間、ループ処理、ベンチマークではStopwatchを使うのが基本です。

また、実務ではタイムゾーンにも注意が必要です。サーバー環境や海外ユーザー対応では、DateTime.Nowに依存すると時刻のずれが発生する可能性があります。保存はUTC、表示時にローカル変換する方針にすると、日時の扱いを統一しやすくなります。

C#のtime処理で迷ったら、次のように考えると整理できます。

目的使う型
現在日時を取得したいDateTimeDateTimeOffset
日付や時刻を操作したいDateTime
時間差や期間を扱いたいTimeSpan
処理時間を測りたいStopwatch
タイムゾーンを意識したいDateTimeOffsetTimeZoneInfo
日付だけ・時刻だけを扱いたいDateOnlyTimeOnly

「いつ」を表すならDateTime、「どれくらい」を表すならTimeSpan、「処理に何ミリ秒かかったか」を測るならStopwatchです。この使い分けを理解しておけば、C#の時間処理で迷う場面は大きく減らせます。