C#で時間を扱う方法を徹底解説|DateTime・TimeSpan・現在時刻・計算・比較までわかる入門ガイド

はじめに

C#で時間を扱う処理は、アプリ開発でほぼ必ず登場します。たとえば、現在時刻を表示する、締切を判定する、作業時間を計算する、ログに日時を残す、APIにUTC時刻を送る、タイマーで一定間隔の処理を実行する、といった場面です。

ただし、C#の時間処理では「日時」「時刻」「経過時間」「タイムゾーン」を混同すると、思わぬバグにつながります。特に、DateTime.Nowをそのまま保存する、文字列のまま日時を比較する、TimeSpan.HoursTimeSpan.TotalHoursを取り違える、といったミスは初心者だけでなく実務でもよく起こります。

この記事では、C#で時間を扱うための基本として、DateTimeTimeSpanDateTimeOffsetDateOnlyTimeOnlyの使い分けから、現在時刻の取得、時間計算、比較、文字列変換、UTC・タイムゾーン、経過時間の測定、待機処理、実践サンプルまで順番に解説します。

1. C#で「時間」を扱う前に押さえる基本

1-1. C#で扱う「日時」と「時間間隔」の違い

C#で時間を扱うときは、まず「ある一点の日時」と「長さとしての時間」を分けて考えることが重要です。

「2026年6月7日 10時30分」のように、カレンダー上の特定の瞬間を表す場合はDateTimeDateTimeOffsetを使います。一方、「3時間」「15分」「2日と4時間」のような時間の長さを表す場合はTimeSpanを使います。

C#
DateTime startAt = new DateTime(2026, 6, 7, 10, 30, 0); // 日時
TimeSpan duration = TimeSpan.FromHours(3); // 時間間隔

DateTime endAt = startAt + duration;

Console.WriteLine(endAt); // 2026/06/07 13:30:00

DateTimeは日付と時刻を表す構造体で、TimeSpanは時間間隔を表す構造体です。Microsoftの公式ドキュメントでも、DateTimeは日付と時刻、TimeSpanは時間間隔を扱う型として説明されています。

1-2. DateTime・TimeSpan・DateTimeOffset・DateOnly・TimeOnlyの使い分け

C#で時間を扱う主な型は、次のように使い分けます。

主な用途
DateTime日付と時刻を扱う2026/06/07 10:30:00
TimeSpan時間の長さ・差分を扱う2時間30分
DateTimeOffsetUTCとの差分付きで日時を扱う2026/06/07 10:30:00 +09:00
DateOnly日付だけを扱う2026/06/07
TimeOnly時刻だけを扱う10:30:00

たとえば、ユーザーの誕生日には時刻が不要なのでDateOnlyが適しています。毎日9時に通知するような処理では、日付ではなく時刻だけが必要なのでTimeOnlyが向いています。TimeOnlyは「毎日の昼食時間」「目覚ましの時刻」のような、日付を持たない時刻を表す型です。

C#
DateOnly birthday = new DateOnly(1995, 4, 10);
TimeOnly alarmTime = new TimeOnly(7, 30);

Console.WriteLine(birthday); // 1995/04/10
Console.WriteLine(alarmTime); // 7:30

海外ユーザーやAPI、ログ、データベース保存など、UTCとの関係を明確にしたい場合はDateTimeOffsetが便利です。DateTimeOffsetは日時に加えてUTCとの差分であるオフセットを持ち、UTCに対する日時をより明確に表せます。

1-3. 初心者が混乱しやすい「日付」「時刻」「経過時間」の考え方

初心者が混乱しやすいポイントは、「時刻」と「時間の長さ」を同じものとして扱ってしまうことです。

たとえば「9時」は1日の中の時刻です。一方で「9時間」は経過時間です。この2つは似ていますが、プログラム上では意味がまったく違います。

C#
TimeOnly meetingTime = new TimeOnly(9, 0);      // 9時
TimeSpan workDuration = TimeSpan.FromHours(9); // 9時間

「2026年6月7日 9時」はDateTime、「9時」はTimeOnly、「9時間」はTimeSpanと考えると整理しやすくなります。

1-4. この記事で作れるようになる時間処理の全体像

この記事を読み終えると、次のようなC#の時間処理を書けるようになります。

C#
DateTime now = DateTime.Now;
DateTime deadline = new DateTime(2026, 6, 10, 18, 0, 0);

if (now > deadline)
{
Console.WriteLine("期限切れです");
}
else
{
TimeSpan remaining = deadline - now;
Console.WriteLine($"残り {remaining.TotalHours:F1} 時間です");
}

このように、現在時刻を取得し、指定日時と比較し、差分をTimeSpanとして求める処理は、C#の時間処理の基本パターンです。

2. DateTimeで日付と時刻を扱う基本

2-1. DateTimeとは何か

DateTimeは、C#で日付と時刻を扱う代表的な型です。年、月、日、時、分、秒、ミリ秒などを持ち、「いつ」を表します。

C#
DateTime dateTime = new DateTime(2026, 6, 7, 10, 30, 0);
Console.WriteLine(dateTime); // 2026/06/07 10:30:00

DateTimeは、予約日時、作成日時、更新日時、締切日時、ログ出力日時など、日付と時刻をまとめて扱いたい場面で使います。

2-2. DateTimeを作成する基本構文

DateTimeはコンストラクターで作成できます。

C#
DateTime date1 = new DateTime(2026, 6, 7);
DateTime date2 = new DateTime(2026, 6, 7, 10, 30, 0);
DateTime date3 = new DateTime(2026, 6, 7, 10, 30, 0, DateTimeKind.Local);

引数を3つ指定すると、年・月・日のみを持つDateTimeになります。この場合、時刻は00:00:00です。

C#
DateTime date = new DateTime(2026, 6, 7);

Console.WriteLine(date); // 2026/06/07 0:00:00

年月日と時分秒を指定すると、特定の日時を作成できます。

C#
DateTime meetingAt = new DateTime(2026, 6, 7, 14, 0, 0);

Console.WriteLine(meetingAt); // 2026/06/07 14:00:00

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

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

C#
DateTime now = new DateTime(2026, 6, 7, 10, 30, 45);

Console.WriteLine(now.Year); // 2026
Console.WriteLine(now.Month); // 6
Console.WriteLine(now.Day); // 7
Console.WriteLine(now.Hour); // 10
Console.WriteLine(now.Minute); // 30
Console.WriteLine(now.Second); // 45

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

C#
DateTime date = new DateTime(2026, 6, 7);

Console.WriteLine(date.DayOfWeek); // Sunday

2-4. DateプロパティとTimeOfDayプロパティの使い方

DateTime.Dateは、日付部分だけを取り出します。ただし戻り値はDateTimeで、時刻は00:00:00になります。

C#
DateTime now = new DateTime(2026, 6, 7, 10, 30, 45);

DateTime dateOnly = now.Date;

Console.WriteLine(dateOnly); // 2026/06/07 0:00:00

TimeOfDayは、時刻部分をTimeSpanとして取得します。

C#
DateTime now = new DateTime(2026, 6, 7, 10, 30, 45);

TimeSpan time = now.TimeOfDay;

Console.WriteLine(time); // 10:30:45

日付だけを比較したい場合はDate、時刻部分の長さを扱いたい場合はTimeOfDayが便利です。

2-5. DateTimeKindでローカル時刻・UTC時刻を区別する

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

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

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

DateTimeKindには、主に次の3つがあります。

意味
Localローカル時刻
UtcUTC時刻
UnspecifiedローカルかUTCか未指定

DateTimeKindは、ToLocalTimeToUniversalTimeで変換するときに重要です。タイムゾーンをまたぐ処理やAPI連携では、KindがあいまいなDateTimeをそのまま扱うと誤変換の原因になります。

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

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

現在のローカル日時を取得するには、DateTime.Nowを使います。

C#
DateTime now = DateTime.Now;

Console.WriteLine(now);

DateTime.Nowは、実行環境のローカルタイムゾーンに基づく現在日時を返します。画面表示、ユーザー向けメッセージ、ローカルPC上の簡単な処理では使いやすい方法です。

C#
Console.WriteLine($"現在時刻: {DateTime.Now}");

ただし、サーバー側で保存する日時や、複数タイムゾーンのユーザーが関係するアプリでは、DateTime.Nowをそのまま保存すると混乱しやすくなります。

3-2. DateTime.Todayで今日の日付だけを取得する

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

C#
DateTime today = DateTime.Today;

Console.WriteLine(today); // 今日の日付 0:00:00

DateTime.Todayは、現在日付の00:00:00を表すDateTimeです。

C#
DateTime today = DateTime.Today;
DateTime tomorrow = today.AddDays(1);

Console.WriteLine(today);
Console.WriteLine(tomorrow);

日付単位の判定では、DateTime.Now.DateよりもDateTime.Todayのほうが意図が明確です。

3-3. DateTime.UtcNowでUTC時刻を取得する

UTCの現在時刻を取得するには、DateTime.UtcNowを使います。

C#
DateTime utcNow = DateTime.UtcNow;

Console.WriteLine(utcNow);
Console.WriteLine(utcNow.Kind); // Utc

UTCは協定世界時で、タイムゾーンに依存しない基準時刻として扱えます。API、ログ、データベース保存、分散システムでは、UTCで保存し、表示時にユーザーのローカル時刻へ変換する設計がよく使われます。

3-4. 現在時刻の取得でNowとUtcNowを使い分ける基準

DateTime.NowDateTime.UtcNowは、用途で使い分けます。

目的推奨
画面に現在時刻を表示するDateTime.Now
ローカルPCだけで完結する処理DateTime.Now
DBに保存する作成日時・更新日時DateTime.UtcNow
APIに送る日時DateTime.UtcNow
複数地域のユーザーが使うサービスDateTime.UtcNowまたはDateTimeOffset

実務では、「保存はUTC、表示はローカル」という方針にすると、タイムゾーンによるズレを減らしやすくなります。

3-5. 現在時刻を文字列として表示するサンプルコード

現在時刻を任意の形式で表示するには、ToStringにフォーマットを指定します。

C#
DateTime now = DateTime.Now;

Console.WriteLine(now.ToString("yyyy/MM/dd HH:mm:ss"));
Console.WriteLine(now.ToString("yyyy年MM月dd日 HH時mm分ss秒"));
Console.WriteLine(now.ToString("HH:mm"));

ログ用なら、年月日と時分秒を固定桁で出力すると扱いやすくなります。

C#
string logTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");

Console.WriteLine($"[{logTime}] 処理を開始しました");

4. TimeSpanで時間間隔を扱う方法

4-1. TimeSpanとは何か

TimeSpanは、時間の長さや日時の差分を表す型です。たとえば「30分」「2時間」「3日と5時間」などを扱います。

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

Console.WriteLine(duration); // 01:30:00

TimeSpanは、待ち時間、処理時間、作業時間、残り時間、日時同士の差分などに使います。TimeSpanは時間間隔を表す構造体で、加算、減算、比較などの演算もサポートされています。

4-2. TimeSpanを作成する基本構文

TimeSpanはコンストラクターまたはFromDaysFromHoursFromMinutesなどのメソッドで作成できます。

C#
TimeSpan time1 = new TimeSpan(1, 30, 0); // 1時間30分
TimeSpan time2 = TimeSpan.FromHours(1.5);
TimeSpan time3 = TimeSpan.FromMinutes(90);

Console.WriteLine(time1);
Console.WriteLine(time2);
Console.WriteLine(time3);

可読性を重視するなら、TimeSpan.FromHoursTimeSpan.FromMinutesを使うと意図が伝わりやすくなります。

C#
TimeSpan retryInterval = TimeSpan.FromSeconds(5);
TimeSpan timeout = TimeSpan.FromMinutes(3);

4-3. 日・時・分・秒・ミリ秒を取得する方法

TimeSpanから日、時間、分、秒、ミリ秒を取得できます。

C#
TimeSpan span = new TimeSpan(2, 3, 4, 5, 600);

Console.WriteLine(span.Days); // 2
Console.WriteLine(span.Hours); // 3
Console.WriteLine(span.Minutes); // 4
Console.WriteLine(span.Seconds); // 5
Console.WriteLine(span.Milliseconds); // 600

ここで注意したいのは、HoursMinutesは「全体の時間数」ではなく、日数などを除いた残り部分を返すことです。

4-4. DaysとTotalDays、HoursとTotalHoursの違い

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

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

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

Daysは「日部分」、Hoursは「時間部分」です。一方、TotalDaysTotalHoursは、時間間隔全体を日数や時間数に換算した値です。

作業時間を「合計何時間か」で出したい場合は、HoursではなくTotalHoursを使います。

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

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

4-5. 経過時間や待ち時間をTimeSpanで表すサンプルコード

処理の開始日時と終了日時の差分を求めると、結果はTimeSpanになります。

C#
DateTime startedAt = new DateTime(2026, 6, 7, 9, 0, 0);
DateTime endedAt = new DateTime(2026, 6, 7, 17, 30, 0);

TimeSpan workTime = endedAt - startedAt;

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

待ち時間やタイムアウト時間もTimeSpanで表すと読みやすくなります。

C#
TimeSpan waitTime = TimeSpan.FromSeconds(10);
TimeSpan timeout = TimeSpan.FromMinutes(1);

Console.WriteLine($"待機時間: {waitTime.TotalSeconds}秒");
Console.WriteLine($"タイムアウト: {timeout.TotalSeconds}秒");

5. C#で時間を計算する方法

5-1. DateTimeに日数や時間を足す方法

DateTimeに日数や時間を足すには、AddDaysAddHoursAddMinutesAddSecondsなどを使います。

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

DateTime after3Days = now.AddDays(3);
DateTime after2Hours = now.AddHours(2);
DateTime after30Minutes = now.AddMinutes(30);

Console.WriteLine(after3Days);
Console.WriteLine(after2Hours);
Console.WriteLine(after30Minutes);

DateTimeは不変の値型なので、AddDaysなどを呼び出しても元の値は変わりません。戻り値を受け取る必要があります。

C#
DateTime date = new DateTime(2026, 6, 7);

date.AddDays(1); // 戻り値を使っていないため、dateは変わらない

Console.WriteLine(date); // 2026/06/07 0:00:00

正しくは次のように書きます。

C#
date = date.AddDays(1);

Console.WriteLine(date); // 2026/06/08 0:00:00

5-2. DateTimeから日数や時間を引く方法

日数や時間を引くには、AddDaysAddHoursに負の値を指定します。

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

DateTime yesterday = now.AddDays(-1);
DateTime oneHourAgo = now.AddHours(-1);

Console.WriteLine(yesterday);
Console.WriteLine(oneHourAgo);

TimeSpanを使って引くこともできます。

C#
DateTime now = DateTime.Now;
TimeSpan span = TimeSpan.FromMinutes(30);

DateTime before30Minutes = now - span;

Console.WriteLine(before30Minutes);

5-3. 2つのDateTimeの差分をTimeSpanで求める方法

2つのDateTimeを引き算すると、差分はTimeSpanになります。

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

TimeSpan diff = end - start;

Console.WriteLine(diff); // 09:00:00
Console.WriteLine(diff.TotalHours); // 9

締切までの残り時間を求める場合も同じです。

C#
DateTime deadline = new DateTime(2026, 6, 10, 18, 0, 0);
TimeSpan remaining = deadline - DateTime.Now;

Console.WriteLine($"残り{remaining.TotalDays:F1}日");

5-4. TimeSpan同士を足し算・引き算する方法

TimeSpan同士は足し算・引き算できます。

C#
TimeSpan morning = TimeSpan.FromHours(3);
TimeSpan afternoon = TimeSpan.FromHours(4.5);

TimeSpan total = morning + afternoon;

Console.WriteLine(total); // 07:30:00
Console.WriteLine(total.TotalHours); // 7.5

引き算も可能です。

C#
TimeSpan planned = TimeSpan.FromHours(8);
TimeSpan actual = TimeSpan.FromHours(6.5);

TimeSpan remaining = planned - actual;

Console.WriteLine(remaining.TotalHours); // 1.5

5-5. AddDays・AddHours・AddMinutes・AddSecondsの使い方

DateTimeの加算では、単位に応じたメソッドを使うと読みやすくなります。

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

Console.WriteLine(baseTime.AddDays(1)); // 1日後
Console.WriteLine(baseTime.AddHours(2)); // 2時間後
Console.WriteLine(baseTime.AddMinutes(30)); // 30分後
Console.WriteLine(baseTime.AddSeconds(10)); // 10秒後

複数の単位を組み合わせることもできます。

C#
DateTime result = baseTime
.AddDays(1)
.AddHours(2)
.AddMinutes(30);

Console.WriteLine(result);

5-6. 月末・うるう年・日付またぎで注意すべきポイント

日付計算では、月末やうるう年に注意が必要です。たとえば、1か月後を計算する場合はAddMonthsを使います。

C#
DateTime date = new DateTime(2026, 1, 31);

DateTime nextMonth = date.AddMonths(1);

Console.WriteLine(nextMonth); // 2026/02/28 0:00:00

月の日数は月によって違うため、「30日後」と「1か月後」は同じ意味ではありません。

C#
DateTime date = new DateTime(2026, 1, 31);

Console.WriteLine(date.AddDays(30));
Console.WriteLine(date.AddMonths(1));

また、日付をまたぐ計算では、単に時刻だけを引くと正しく計算できないことがあります。

C#
DateTime start = new DateTime(2026, 6, 7, 22, 0, 0);
DateTime end = new DateTime(2026, 6, 8, 2, 0, 0);

TimeSpan duration = end - start;

Console.WriteLine(duration.TotalHours); // 4

深夜勤務や利用時間の計算では、必ず日付を含めて計算するのが安全です。

6. C#で時間を比較する方法

6-1. DateTime同士を比較する基本

DateTime同士は、前後関係を比較できます。

C#
DateTime a = new DateTime(2026, 6, 7, 10, 0, 0);
DateTime b = new DateTime(2026, 6, 7, 12, 0, 0);

Console.WriteLine(a < b); // True
Console.WriteLine(a > b); // False
Console.WriteLine(a == b); // False

日時の比較は、予約日時、期限、開始・終了判定などでよく使います。

6-2. 比較演算子で日時の前後を判定する方法

期限切れを判定する例です。

C#
DateTime deadline = new DateTime(2026, 6, 7, 18, 0, 0);

if (DateTime.Now > deadline)
{
Console.WriteLine("期限切れです");
}
else
{
Console.WriteLine("まだ期限内です");
}

開始日時と終了日時の整合性チェックにも使えます。

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

if (start >= end)
{
Console.WriteLine("終了日時は開始日時より後にしてください");
}

6-3. CompareToメソッドとDateTime.Compareの使い方

CompareToを使うと、比較結果を数値で取得できます。

C#
DateTime a = new DateTime(2026, 6, 7);
DateTime b = new DateTime(2026, 6, 8);

int result = a.CompareTo(b);

Console.WriteLine(result); // 0より小さい

DateTime.Compareでも比較できます。

C#
int result = DateTime.Compare(a, b);

if (result < 0)
{
Console.WriteLine("aはbより前です");
}
else if (result > 0)
{
Console.WriteLine("aはbより後です");
}
else
{
Console.WriteLine("同じ日時です");
}

通常の条件分岐では比較演算子のほうが読みやすく、並び替えや汎用的な比較処理ではCompareToDateTime.Compareが使いやすいです。

6-4. 日付だけを比較して時刻を無視する方法

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

C#
DateTime a = new DateTime(2026, 6, 7, 9, 0, 0);
DateTime b = new DateTime(2026, 6, 7, 18, 0, 0);

if (a.Date == b.Date)
{
Console.WriteLine("同じ日付です");
}

今日かどうかを判定する場合は、次のように書けます。

C#
DateTime target = new DateTime(2026, 6, 7, 15, 30, 0);

if (target.Date == DateTime.Today)
{
Console.WriteLine("今日です");
}

.NET環境でDateOnlyが使える場合は、日付だけを表す型としてDateOnlyを使う方法もあります。

C#
DateOnly date1 = new DateOnly(2026, 6, 7);
DateOnly date2 = DateOnly.FromDateTime(DateTime.Now);

if (date1 == date2)
{
Console.WriteLine("同じ日付です");
}

6-5. 時刻だけを比較する方法

時刻だけを比較したい場合は、TimeOnlyを使うと意図が明確です。

C#
TimeOnly open = new TimeOnly(9, 0);
TimeOnly close = new TimeOnly(18, 0);
TimeOnly now = TimeOnly.FromDateTime(DateTime.Now);

if (now >= open && now < close)
{
Console.WriteLine("営業時間内です");
}
else
{
Console.WriteLine("営業時間外です");
}

DateTimeしか使えない環境では、TimeOfDayで時刻部分をTimeSpanとして比較できます。

C#
TimeSpan open = new TimeSpan(9, 0, 0);
TimeSpan close = new TimeSpan(18, 0, 0);
TimeSpan now = DateTime.Now.TimeOfDay;

if (now >= open && now < close)
{
Console.WriteLine("営業時間内です");
}

ただし、深夜をまたぐ時間帯では単純な比較では足りません。

C#
TimeOnly start = new TimeOnly(22, 0);
TimeOnly end = new TimeOnly(5, 0);
TimeOnly current = new TimeOnly(1, 0);

bool isInRange = current >= start || current < end;

Console.WriteLine(isInRange); // True

6-6. TimeSpan同士を比較する方法

TimeSpanも比較できます。

C#
TimeSpan limit = TimeSpan.FromHours(8);
TimeSpan actual = TimeSpan.FromHours(9);

if (actual > limit)
{
Console.WriteLine("上限時間を超えています");
}

処理時間、利用時間、作業時間などを比較するときに便利です。

C#
TimeSpan timeout = TimeSpan.FromSeconds(5);
TimeSpan elapsed = TimeSpan.FromSeconds(3);

Console.WriteLine(elapsed <= timeout); // True

7. 日時や時間を文字列に変換・解析する方法

7-1. DateTimeをToStringで表示形式に変換する

DateTimeを文字列として表示するには、ToStringを使います。

C#
DateTime now = DateTime.Now;

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

フォーマットを指定すると、表示形式を自由に変更できます。

C#
DateTime date = new DateTime(2026, 6, 7, 10, 30, 45);

string text = date.ToString("yyyy年MM月dd日 HH時mm分ss秒");

Console.WriteLine(text);

7-2. よく使う日付・時刻フォーマット指定子

よく使うフォーマット指定子は次のとおりです。

指定子意味
yyyy4桁の年2026
MM2桁の月06
dd2桁の日07
HH24時間表記の時10
mm30
ss45
fffミリ秒123
C#
DateTime now = DateTime.Now;

Console.WriteLine(now.ToString("yyyyMMdd"));
Console.WriteLine(now.ToString("yyyy-MM-dd"));
Console.WriteLine(now.ToString("HH:mm:ss"));
Console.WriteLine(now.ToString("yyyy-MM-ddTHH:mm:ss"));

ログやファイル名では、並び替えやすい形式にすると便利です。

C#
string fileName = $"log_{DateTime.Now:yyyyMMdd_HHmmss}.txt";

Console.WriteLine(fileName);

7-3. Parseで文字列をDateTimeに変換する

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

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

DateTime dateTime = DateTime.Parse(text);

Console.WriteLine(dateTime);

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

C#
string text = "invalid date";

// DateTime.Parse(text); // FormatException

ユーザー入力を扱う場合は、後述するTryParseを使うほうが安全です。

7-4. TryParseで安全に日時文字列を変換する

TryParseは、変換に成功したかどうかをboolで返します。失敗しても例外が発生しないため、ユーザー入力や外部データの変換に向いています。

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

if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"変換成功: {result}");
}
else
{
Console.WriteLine("日時として変換できません");
}

不正な値を受け取る可能性がある場面では、ParseよりTryParseを優先すると安定したコードになります。

7-5. ParseExactで指定フォーマットの日時を変換する

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

C#
using System.Globalization;

string input = "2026-06-07 10:30:00";

DateTime dateTime = DateTime.ParseExact(
input,
"yyyy-MM-dd HH:mm:ss",
CultureInfo.InvariantCulture
);

Console.WriteLine(dateTime);

安全に変換したい場合は、TryParseExactを使います。

C#
using System.Globalization;

string input = "2026-06-07";

if (DateTime.TryParseExact(
input,
"yyyy-MM-dd",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out DateTime result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("指定形式ではありません");
}

APIやCSVなど、日時フォーマットが厳密に決まっているデータではParseExactTryParseExactが適しています。

7-6. 変換エラーを防ぐための実装ポイント

日時文字列の変換エラーを防ぐには、次の点を意識します。

ポイント理由
ユーザー入力にはTryParseを使う例外を避けられる
形式が決まっている場合はTryParseExactを使う想定外の形式を弾ける
API送受信ではISO 8601形式を検討するタイムゾーンを扱いやすい
文字列のまま日時比較しない形式違いで誤判定しやすい
カルチャ差を考慮するMM/dd/yyyydd/MM/yyyyなどの違いがある

特に、日付の文字列は国や環境によって解釈が変わる可能性があります。内部処理ではできるだけDateTimeDateTimeOffsetに変換して扱いましょう。

8. タイムゾーンとUTCを扱う方法

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

ローカル時刻は、実行環境やユーザーの地域に依存する時刻です。一方、UTCは世界共通の基準時刻です。

日本時間は通常UTC+09:00なので、日本時間の2026年6月7日 18:00は、UTCでは2026年6月7日 09:00です。

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

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

グローバルなWebアプリやAPIでは、サーバーのローカル時刻ではなくUTCを基準にすることで、環境差によるバグを減らせます。

8-2. ToLocalTimeとToUniversalTimeの使い方

DateTimeをローカル時刻やUTCに変換するには、ToLocalTimeToUniversalTimeを使います。

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

Console.WriteLine(utc);
Console.WriteLine(local);

ローカル時刻をUTCへ変換する場合は、ToUniversalTimeを使います。

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

Console.WriteLine(utc);

ただし、DateTimeKindUnspecifiedの値を変換すると、意図しない前提で変換されることがあります。変換前に、その日時がローカルなのかUTCなのかを明確にしておくことが重要です。

8-3. TimeZoneInfoでタイムゾーン変換する方法

任意のタイムゾーンへ変換するには、TimeZoneInfoを使います。TimeZoneInfoは、ローカルタイムゾーンだけでなく、任意のタイムゾーンを表し、別のタイムゾーンへの変換に利用できます。

C#
DateTime utcNow = DateTime.UtcNow;

TimeZoneInfo tokyo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, tokyo);

Console.WriteLine(tokyoTime);

Windowsでは"Tokyo Standard Time"のようなIDが使われます。LinuxやmacOSでは環境によってIANAタイムゾーンIDを使う場合があります。クロスプラットフォームで運用する場合は、実行環境のタイムゾーンIDに注意してください。

8-4. DateTimeOffsetを使うべきケース

DateTimeOffsetは、日時とUTCからのオフセットを一緒に持つ型です。

C#
DateTimeOffset now = DateTimeOffset.Now;

Console.WriteLine(now); // 例: 2026/06/07 10:30:00 +09:00
Console.WriteLine(now.Offset); // 09:00:00

次のようなケースではDateTimeOffsetが向いています。

ケース理由
APIで日時を送受信するオフセット込みで意味が明確になる
ユーザーの現地時刻を保存するどのUTC差分の時刻か残せる
ログに発生時刻を残す後から時刻の意味を追いやすい
複数タイムゾーンを扱うローカル時刻だけより誤解が少ない

DateTimeOffsetDateTimeよりもタイムゾーンを意識した日時表現に向いていますが、完全なタイムゾーン情報そのものを持つわけではありません。保持しているのはUTCとの差分です。

8-5. WebアプリやAPIで時刻を扱うときの注意点

WebアプリやAPIで時刻を扱う場合は、次の方針が基本です。

処理推奨
DB保存UTCまたはDateTimeOffset
API送信UTCまたはISO 8601形式
画面表示ユーザーのタイムゾーンへ変換
比較・集計基準をUTCにそろえる
ログUTCまたはオフセット付き日時

避けたいのは、サーバーのDateTime.Nowをそのまま保存し、別の地域のユーザーにそのまま表示することです。開発者の環境では正しく見えても、本番環境や海外ユーザーでは時刻がずれる可能性があります。

9. 経過時間を測定する方法

9-1. DateTimeで経過時間を測る方法

簡単な経過時間であれば、開始時刻と終了時刻の差分を取ることで測定できます。

C#
DateTime start = DateTime.Now;

// 何らかの処理
Thread.Sleep(1000);

DateTime end = DateTime.Now;

TimeSpan elapsed = end - start;

Console.WriteLine(elapsed.TotalSeconds);

ただし、処理時間の測定にはDateTimeよりStopwatchのほうが適しています。DateTime.Nowは現在時刻を取得するためのものであり、システム時刻の変更などの影響を受ける可能性があります。

9-2. Stopwatchで正確に処理時間を測る方法

処理時間を測定するには、System.Diagnostics.Stopwatchを使います。Stopwatchは経過時間を正確に測定するためのメソッドやプロパティを提供するクラスです。

C#
using System.Diagnostics;

Stopwatch stopwatch = Stopwatch.StartNew();

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

stopwatch.Stop();

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

ElapsedTimeSpanElapsedMillisecondsは合計ミリ秒を返します。Stopwatch.Elapsedは、測定した経過時間をTimeSpanとして取得できます。

9-3. DateTimeとStopwatchの使い分け

DateTimeStopwatchは、目的が違います。

目的使う型
現在日時を知りたいDateTime
予約日時や期限を扱いたいDateTime
処理に何秒かかったか測りたいStopwatch
パフォーマンス測定をしたいStopwatch

日時を扱うならDateTime、経過時間を測るならStopwatchと覚えるとよいでしょう。

9-4. 処理時間をミリ秒・秒で表示するサンプルコード

C#
using System.Diagnostics;

Stopwatch sw = Stopwatch.StartNew();

for (int i = 0; i < 1000000; i++)
{
// 測定対象の処理
}

sw.Stop();

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

小数点付きの秒数で表示したい場合は、Elapsed.TotalSecondsを使います。

C#
Console.WriteLine($"{sw.Elapsed.TotalSeconds:F2}秒");

10. 待機・タイマー・定期実行で時間を使う方法

10-1. Task.Delayで一定時間待機する方法

非同期処理で一定時間待機するには、Task.Delayを使います。Task.Delayは、指定した時間だけタスクの処理を遅延させるために使われます。

C#
await Task.Delay(1000); // 1秒待機

TimeSpanを渡すと、意図が読みやすくなります。

C#
await Task.Delay(TimeSpan.FromSeconds(5));

リトライ処理の待機にもよく使います。

C#
for (int i = 0; i < 3; i++)
{
Console.WriteLine("処理を試行します");

await Task.Delay(TimeSpan.FromSeconds(2));
}

10-2. Thread.Sleepとの違いと注意点

Thread.Sleepも待機に使えますが、現在のスレッドをブロックします。

C#
Thread.Sleep(1000);

一方、Task.Delayは非同期的に待機できるため、非同期メソッドではTask.Delayを使うのが基本です。

C#
public async Task WaitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
}

WebアプリやGUIアプリでThread.Sleepを使うと、スレッドを占有して応答性が悪くなることがあります。非同期処理ではawait Task.Delay(...)を使いましょう。

10-3. Timerを使って一定間隔で処理を実行する方法

一定間隔で処理を実行したい場合は、System.Threading.Timerを使えます。Timerは、指定した間隔でスレッドプール上のメソッドを実行する仕組みを提供します。

C#
using System.Threading;

Timer timer = new Timer(_ =>
{
Console.WriteLine($"実行: {DateTime.Now:HH:mm:ss}");
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

この例では、最初はすぐ実行し、その後5秒ごとに処理を実行します。

アプリ終了までタイマーを使う場合は、タイマーのインスタンスがガベージコレクションされないように保持しておく必要があります。また、不要になったらDisposeで破棄します。

C#
timer.Dispose();

10-4. 非同期処理で時間を扱うときの基本

非同期処理では、asyncawaitを使い、待機にはTask.Delayを使います。

C#
public async Task RunAsync()
{
Console.WriteLine("開始");

await Task.Delay(TimeSpan.FromSeconds(3));

Console.WriteLine("終了");
}

定期実行を簡単に書くなら、ループとTask.Delayを組み合わせる方法もあります。

C#
public async Task RunLoopAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"実行: {DateTime.Now}");

await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
}
}

キャンセルできるようにCancellationTokenを受け取ると、アプリ終了時や処理中断時に安全に止められます。

10-5. タイムアウト処理を実装する方法

非同期処理にタイムアウトを設定するには、CancellationTokenSourceを使う方法があります。

C#
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

try
{
await Task.Delay(TimeSpan.FromSeconds(10), cts.Token);
Console.WriteLine("完了");
}
catch (TaskCanceledException)
{
Console.WriteLine("タイムアウトしました");
}

また、処理タスクとTask.Delayを競争させる方法もあります。

C#
Task work = DoWorkAsync();
Task timeout = Task.Delay(TimeSpan.FromSeconds(5));

Task completed = await Task.WhenAny(work, timeout);

if (completed == timeout)
{
Console.WriteLine("タイムアウトしました");
}
else
{
Console.WriteLine("処理が完了しました");
}

11. C#の時間処理でよくあるミスと対策

11-1. DateTime.Nowをそのまま保存して起きる問題

DateTime.Nowはローカル時刻です。サーバーのタイムゾーンに依存するため、別の環境へ移行したときや、複数地域のユーザーが使うアプリでは問題になることがあります。

C#
DateTime createdAt = DateTime.Now; // 保存用途では注意

DBに保存する作成日時・更新日時は、UTCで保存する設計がよく使われます。

C#
DateTime createdAt = DateTime.UtcNow;

または、オフセットも含めたい場合はDateTimeOffsetを使います。

C#
DateTimeOffset createdAt = DateTimeOffset.UtcNow;

11-2. タイムゾーンを考慮しない比較の危険性

異なるタイムゾーンの日時をそのまま比較すると、意図しない結果になることがあります。

C#
DateTime tokyoTime = new DateTime(2026, 6, 7, 18, 0, 0);
DateTime utcTime = new DateTime(2026, 6, 7, 9, 0, 0);

Console.WriteLine(tokyoTime == utcTime); // False

この2つは日本時間とUTCで同じ瞬間を表している可能性がありますが、DateTimeの値だけを見ると別の日時です。比較前にUTCへそろえる、またはDateTimeOffsetを使うことを検討しましょう。

C#
DateTimeOffset tokyo = new DateTimeOffset(2026, 6, 7, 18, 0, 0, TimeSpan.FromHours(9));
DateTimeOffset utc = new DateTimeOffset(2026, 6, 7, 9, 0, 0, TimeSpan.Zero);

Console.WriteLine(tokyo.ToUniversalTime() == utc.ToUniversalTime()); // True

11-3. 文字列比較で日時を判定してはいけない理由

日時を文字列のまま比較すると、形式によっては正しく判定できません。

C#
string a = "2026/6/7";
string b = "2026/12/1";

Console.WriteLine(string.Compare(a, b) < 0);

文字列比較は辞書順で比較されるため、日時としての前後関係と一致しない場合があります。日時はDateTimeに変換して比較しましょう。

C#
DateTime a = DateTime.Parse("2026/6/7");
DateTime b = DateTime.Parse("2026/12/1");

Console.WriteLine(a < b); // True

11-4. Total系プロパティと通常プロパティの取り違え

TimeSpan.Hoursは合計時間ではありません。日数を除いた「時間部分」です。

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

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

作業時間や利用時間を合計時間として表示するなら、TotalHoursを使います。

C#
Console.WriteLine($"利用時間: {span.TotalHours}時間");

分単位ならTotalMinutes、秒単位ならTotalSecondsを使います。

11-5. null許容の日時を扱うときの注意点

日時が未設定になる可能性がある場合は、DateTime?を使います。

C#
DateTime? completedAt = null;

if (completedAt.HasValue)
{
Console.WriteLine(completedAt.Value);
}
else
{
Console.WriteLine("未完了です");
}

DateTime?をそのままDateTimeとして扱おうとするとエラーになります。値があるか確認してから使いましょう。

C#
DateTime? reservedAt = GetReservedAt();

if (reservedAt is not null)
{
Console.WriteLine(reservedAt.Value.ToString("yyyy/MM/dd HH:mm"));
}

デフォルト値が必要な場合は、GetValueOrDefault??を使えます。

C#
DateTime displayDate = reservedAt ?? DateTime.Today;

11-6. テストしやすい時間処理を書くための考え方

DateTime.Nowをコードの中で直接呼び出すと、テスト時に現在時刻を固定しづらくなります。

C#
public bool IsExpired(DateTime deadline)
{
return DateTime.Now > deadline;
}

テストしやすくするには、現在時刻を外から渡します。

C#
public bool IsExpired(DateTime deadline, DateTime now)
{
return now > deadline;
}

呼び出し側で現在時刻を渡します。

C#
bool expired = IsExpired(deadline, DateTime.UtcNow);

より本格的には、現在時刻を返すインターフェースやTimeProviderを使って、時間依存の処理を分離します。

12. C#の時間処理でよく使う実践サンプル

12-1. 現在時刻から期限切れを判定する

C#
DateTime deadline = new DateTime(2026, 6, 10, 18, 0, 0);
DateTime now = DateTime.Now;

if (now > deadline)
{
Console.WriteLine("期限切れです");
}
else
{
Console.WriteLine("期限内です");
}

残り時間も表示する場合は、差分をTimeSpanで求めます。

C#
TimeSpan remaining = deadline - now;

if (remaining > TimeSpan.Zero)
{
Console.WriteLine($"残り {remaining.TotalHours:F1} 時間です");
}
else
{
Console.WriteLine("期限切れです");
}

12-2. 開始時刻と終了時刻から作業時間を求める

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

TimeSpan workTime = end - start;

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

休憩時間を引く場合は、TimeSpan同士で計算します。

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

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

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

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

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

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

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

12-4. 日付をまたぐ勤務時間や利用時間を計算する

開始が22時、終了が翌2時のように日付をまたぐ場合は、必ず日付付きのDateTimeで計算します。

C#
DateTime start = new DateTime(2026, 6, 7, 22, 0, 0);
DateTime end = new DateTime(2026, 6, 8, 2, 0, 0);

TimeSpan duration = end - start;

Console.WriteLine($"利用時間: {duration.TotalHours}時間");

時刻だけで受け取った場合は、終了時刻が開始時刻より前なら翌日として扱う方法があります。

C#
DateTime date = new DateTime(2026, 6, 7);

TimeSpan startTime = new TimeSpan(22, 0, 0);
TimeSpan endTime = new TimeSpan(2, 0, 0);

DateTime start = date + startTime;
DateTime end = date + endTime;

if (end <= start)
{
end = end.AddDays(1);
}

TimeSpan duration = end - start;

Console.WriteLine(duration.TotalHours); // 4

12-5. ログ出力用に現在時刻をフォーマットする

ログでは、年月日、時分秒、ミリ秒まで出すと追跡しやすくなります。

C#
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");

Console.WriteLine($"[{timestamp}] 処理を開始しました");

UTCでログを出すなら、次のようにします。

C#
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

Console.WriteLine($"[{timestamp}] API request started");

オフセット付きで出したい場合はDateTimeOffsetを使います。

C#
string timestamp = DateTimeOffset.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz");

Console.WriteLine($"[{timestamp}] 処理を開始しました");

12-6. API送信用にUTC時刻へ変換する

APIへ日時を送る場合は、UTCへ変換して送ると扱いやすくなります。

C#
DateTime localTime = DateTime.Now;
DateTime utcTime = localTime.ToUniversalTime();

string apiValue = utcTime.ToString("yyyy-MM-ddTHH:mm:ssZ");

Console.WriteLine(apiValue);

DateTimeOffsetを使う場合は、ToUniversalTimeでUTCに変換できます。

C#
DateTimeOffset localTime = DateTimeOffset.Now;
DateTimeOffset utcTime = localTime.ToUniversalTime();

Console.WriteLine(utcTime.ToString("yyyy-MM-ddTHH:mm:sszzz"));

APIの仕様でISO 8601形式が求められる場合は、仕様に合わせてフォーマットを統一しましょう。

13. C#で時間を扱う方法に関するよくある質問

13-1. DateTimeとTimeSpanの違いは何ですか

DateTimeは「ある日時」を表し、TimeSpanは「時間の長さ」を表します。

C#
DateTime dateTime = new DateTime(2026, 6, 7, 10, 0, 0);
TimeSpan span = TimeSpan.FromHours(2);

「2026年6月7日10時」はDateTime、「2時間」はTimeSpanです。2つのDateTimeの差を求めると、結果はTimeSpanになります。

C#
DateTime start = new DateTime(2026, 6, 7, 10, 0, 0);
DateTime end = new DateTime(2026, 6, 7, 12, 0, 0);

TimeSpan diff = end - start;

13-2. DateTime.NowとDateTime.UtcNowはどちらを使うべきですか

画面表示など、ローカル時刻が必要な場面ではDateTime.Nowを使います。DB保存、API連携、ログ、複数タイムゾーン対応ではDateTime.UtcNowを使うのが基本です。

C#
DateTime displayTime = DateTime.Now;
DateTime savedTime = DateTime.UtcNow;

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

13-3. 日付だけ・時刻だけを扱うにはどうすればよいですか

日付だけならDateOnly、時刻だけならTimeOnlyが使えます。

C#
DateOnly date = new DateOnly(2026, 6, 7);
TimeOnly time = new TimeOnly(10, 30);

DateTimeを使う場合、日付だけを比較するならDate、時刻だけを取得するならTimeOfDayを使います。

C#
DateTime now = DateTime.Now;

DateTime datePart = now.Date;
TimeSpan timePart = now.TimeOfDay;

13-4. 2つの日時の差を時間単位で求めるにはどうすればよいですか

2つのDateTimeを引き算し、TimeSpan.TotalHoursを使います。

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

TimeSpan diff = end - start;

Console.WriteLine(diff.TotalHours); // 8.5

diff.Hoursでは合計時間ではなく「時間部分」しか取得できないため、合計時間が必要な場合はTotalHoursを使いましょう。

13-5. C#で時間の比較がうまくいかない原因は何ですか

よくある原因は次のとおりです。

原因対策
文字列のまま比較しているDateTimeに変換して比較する
タイムゾーンがそろっていないUTCへ変換して比較する
日付も含めるべき場面で時刻だけ比較しているDateTimeで比較する
DateTimeKindが不明確UtcLocalUnspecifiedを意識する
HoursTotalHoursを混同している合計値にはTotal系を使う

特に、APIやDBから取得した日時は、UTCなのかローカル時刻なのかを確認してから比較することが大切です。

13-6. DateTimeOffsetはいつ使うべきですか

DateTimeOffsetは、UTCとの差分を含めて日時を扱いたいときに使います。

C#
DateTimeOffset now = DateTimeOffset.Now;

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

API、ログ、海外ユーザー対応、イベント発生時刻の記録など、「この日時がUTCからどれだけずれた地域の時刻なのか」を残したい場合に便利です。

一方で、単純に日付と時刻だけを扱うローカルアプリではDateTimeで十分なこともあります。タイムゾーンやUTCとの関係を明確にしたいならDateTimeOffsetを検討しましょう。

まとめ

C#で時間を扱うときは、まず「日時」「時間間隔」「日付だけ」「時刻だけ」「UTCとの差分付き日時」を分けて考えることが大切です。

DateTimeは日付と時刻、TimeSpanは時間の長さ、DateTimeOffsetはオフセット付きの日時、DateOnlyは日付だけ、TimeOnlyは時刻だけを扱います。現在時刻を取得するにはDateTime.NowDateTime.UtcNowを使い、保存やAPI連携ではUTCを基準にすると安全です。

時間計算ではAddDaysAddHoursAddMinutesなどを使い、2つの日時の差分はTimeSpanで取得します。比較では、文字列のまま判定せず、DateTimeDateTimeOffsetに変換してから比較しましょう。

また、処理時間の測定にはDateTimeではなくStopwatch、非同期の待機にはTask.Delay、定期実行にはTimerなど、目的に合った仕組みを使うことが重要です。

C#の時間処理は一見シンプルですが、タイムゾーン、UTC、日付またぎ、文字列変換、合計時間の扱いなどでバグが起きやすい分野です。基本の型と使い分けを理解しておけば、現在時刻の取得、時間計算、比較、ログ出力、API連携まで安定した実装ができるようになります。