プログラミングにおいて日付・時間の処理は非常に重要でありながら、タイムゾーンや夏時間などの複雑な要素が絡み、バグの温床になりやすい分野でもあります。
本記事では主に以下について説明します。
- C#における日時型の全体像と使い分け
- DateTime・DateTimeOffsetの基本操作
- フォーマットと解析
- タイムゾーンと夏時間の正しい扱い方
- DateOnly・TimeOnlyによる新しいアプローチ(.NET 6以降)
- TimeProviderによるテスト容易性の向上(.NET 8以降)
以下のような方に役立つ内容となっています。
- 日時処理の基本を理解したい初心者の方
- タイムゾーンや夏時間で困った経験がある方
- .NET 6以降の新しい日時型を活用したい方
日時処理は見た目以上に複雑で、間違いやすい分野です。
今日は、C#における日時処理の基本から最新の機能まで、一緒に理解を深めていきましょう!
以下に動画もあります。
C#における日時型の全体像
C#では、用途に応じて複数の日時型が用意されています。
以下は日時を表現する型の一覧です。
型 | 主な用途 | タイムゾーン情報 | 導入バージョン |
---|---|---|---|
DateTime | 汎用的な日時処理 | Kind(Utc/Local/Unspecified) | .NET Framework 1.0 (Utc/Localは2.0から) |
DateTimeOffset | UTC基準オフセット付き日時 | 固定オフセット(+09:00など) | .NET Framework 3.5 |
TimeSpan | 時間間隔・差分 | なし | .NET Framework 1.0 |
DateOnly | 日付のみ | なし | .NET 6 |
TimeOnly | 時刻のみ | なし | .NET 6 |
異なる地域のユーザが利用するシステムや、グローバル展開するアプリでは、時差による不具合を避けるためにDateTimeOffsetを選択しましょう。
以下は時間に関するサポート型です。
型 | 主な用途 | 導入バージョン |
---|---|---|
TimeProvider | 時間の抽象化・テスト支援 | .NET 8 |
TimeZoneInfo | タイムゾーン情報の管理 | .NET Framework 3.5 |
夏時間などを適切に扱うためには、DateTimeOffsetに加えてタイムゾーン情報(TimeZoneInfo)も組み合わせる必要があります。
以降、それぞれの日時を表す型の役割やその計算方法・表示方法など具体的なトピックをみていきましょう。
DateTimeとDateTimeOffset
日時処理の扱いで複雑なものの1つに異なるタイムゾーンの扱いがあります。
当初はDateTime型だけだったのですが、異なるタイムゾーンをより正確に扱えるようにするため、DateTimeOffset型が登場しました。それぞれみてみましょう。
DateTime型の基本的な仕組み
DateTime型は、C#で最も基本的な日時型です。しかし、タイムゾーン情報の扱いに制限があります。
DateTime now = DateTime.Now; // ローカルの現在時刻
DateTime utcNow = DateTime.UtcNow; // UTCの現在時刻
DateTime unspecified = new DateTime(2024, 12, 25, 10, 30, 0); // 未指定
Console.WriteLine($"Now: {now} (Kind: {now.Kind})");
// 2025/05/30 17:20:37 (Kind: Local)
Console.WriteLine($"UtcNow: {utcNow} (Kind: {utcNow.Kind})");
// 2025/05/30 8:20:37 (Kind: Utc)
Console.WriteLine($"Unspecified: {unspecified} (Kind: {unspecified.Kind})");
// 2024/12/25 10:30:00 (Kind: Unspecified)
DateTime.KindプロパティはLocal・Utc・Unspecifiedの3種類が設定されますが、実際のタイムゾーン情報は保持されません。
DateTime型は日本国内など単一タイムゾーンでのみ使用する場合は問題ないです。
しかし、複数のタイムゾーンを扱う必要がある場合は、情報が不足してしまいます。
DateTimeOffset型の優位性
DateTimeOffset型は、上述した問題を解決するために設計された型です。
DateTimeOffset型は、日時 + UTCからのオフセットの組み合わせで構成されています。
DateTimeOffset now = DateTimeOffset.Now; // 現在のローカル時刻
DateTimeOffset utcNow = DateTimeOffset.UtcNow; // 現在のUTC時刻
DateTimeOffset specific =
new DateTimeOffset(2024, 12, 25, 10, 30, 0, TimeSpan.FromHours(9));
// 「 TimeSpan.FromHours(9)」で日時の差「9時間」となる
Console.WriteLine($"Now: {now}");
//Now: 2025/05/30 17:23:27 +09:00
Console.WriteLine($"UtcNow: {utcNow}");
//UtcNow: 2025/05/30 8:23:27 +00:00
Console.WriteLine($"Specific: {specific}");
//2024/12/25 10:30:00 +09:00
なるほど! 「協定世界時(UTC)の日時」+「UTCからの時間差」で保存されているんだね。
DateTimeOffset型を使うと以下のように、違うタイムゾーンにおける日時について、正確に比較を行えます。
DateTimeOffset tokyo = new DateTimeOffset(2024, 12, 25, 10, 30, 0, TimeSpan.FromHours(9));
DateTimeOffset newYork = new DateTimeOffset(2024, 12, 24, 20, 30, 0, TimeSpan.FromHours(-5));
Console.WriteLine($"{tokyo == newYork}"); // True
Console.WriteLine($"{tokyo.UtcDateTime}"); // 2024/12/25 1:30:00
Console.WriteLine($"{newYork.UtcDateTime}"); // 2024/12/25 1:30:00
複数の場所・システム間で時刻を共有する場合は、DateTimeOffsetの利用も検討しましょう。
DateTime、DateTimeOffsetの定義(Microsoft Learn)も参考にしてください。
日時の基本操作
現在時刻の取得
現在時刻を取得する方法は複数ありますが、目的に応じて適切な方法を選択することが重要です。
// 現在のローカル時刻を取得
DateTime localNow = DateTime.Now;
Console.WriteLine($"現在のローカル時刻: {localNow}"); //2025/06/04 10:14:11
// 現在のUTC時刻を取得
DateTime utcNow = DateTime.UtcNow;
Console.WriteLine($"現在のUTC時刻: {utcNow}"); //2025/06/04 1:14:11
// 現在の日付のみを取得
DateTime today = DateTime.Today;
Console.WriteLine($"今日の日付: {today}"); //2025/06/04 0:00:00
// DateTimeOffsetを使用(タイムゾーン情報付き)
DateTimeOffset nowWithOffset = DateTimeOffset.Now;
Console.WriteLine($"現在時刻(オフセット付き): {nowWithOffset}");
//2025/06/04 10:14:11 +09:00
DateTimeOffset utcWithOffset = DateTimeOffset.UtcNow;
Console.WriteLine($"UTC時刻(オフセット付き): {utcWithOffset}");
//2025/06/04 1:14:11 +00:00
時間を加算・減算
日時に対して時間を加算・減算する操作は、Add系メソッド(もしくは、Subtract系メソッド)を使用します。
DateTime baseDate = new DateTime(2024, 12, 25, 10, 30, 0);
// 時間の加算
DateTime afterOneHour = baseDate.AddHours(1);
DateTime afterThreeDays = baseDate.AddDays(3);
DateTime afterOneMonth = baseDate.AddMonths(1);
DateTime afterTwoYears = baseDate.AddYears(2);
Console.WriteLine($"基準時刻: {baseDate}"); // 2024/12/25 10:30:00
Console.WriteLine($"1時間後: {afterOneHour}"); // 2024/12/25 11:30:00
Console.WriteLine($"3日後: {afterThreeDays}"); // 2024/12/28 10:30:00
Console.WriteLine($"1ヶ月後: {afterOneMonth}");// 2025/01/25 10:30:00
Console.WriteLine($"2年後: {afterTwoYears}"); // 2026/12/25 10:30:00
// 時間の減算
DateTime oneHourAgo = baseDate.AddHours(-1);
DateTime threeDaysAgo = baseDate.AddDays(-3);
Console.WriteLine($"1時間前: {oneHourAgo}"); // 2024/12/25 9:30:00
Console.WriteLine($"3日前: {threeDaysAgo}"); // 2024/12/22 10:30:00
TimeSpanを使った計算も可能です。
DateTime baseDate = new DateTime(2024, 12, 25, 10, 30, 0);
TimeSpan twoHours = TimeSpan.FromHours(2);
TimeSpan thirtyMinutes = TimeSpan.FromMinutes(30);
DateTime result1 = baseDate + twoHours; // 加算
DateTime result2 = baseDate - thirtyMinutes; // 減算
Console.WriteLine($"2時間後: {result1}"); // 2024/12/25 12:30:00
Console.WriteLine($"30分前: {result2}"); // 2024/12/25 10:00:00
DateTimeOffsetでも同様の操作が可能です。オフセット情報は計算後も保持されるので安心です。
比較
日時の比較は、比較演算子やCompare系メソッドを使用します。
DateTime date1 = new DateTime(2024, 12, 25, 10, 30, 0);
DateTime date2 = new DateTime(2024, 12, 25, 15, 45, 0);
DateTime date3 = new DateTime(2024, 12, 25, 10, 30, 0);
// 比較演算子
Console.WriteLine($"{date1 < date2}"); // True
Console.WriteLine($"{date1 > date2}"); // False
Console.WriteLine($"{date1 == date3}"); // True
// Compareメソッド
int comparison = DateTime.Compare(date1, date2);
Console.WriteLine($"{comparison}"); // -1 (date1 < date2)
// 日付のみの比較
Console.WriteLine($"{date1.Date == date2.Date}"); // True
Compareは-1,0,1でどちらが大きいか(等しいか)がわかるから、大小比較、ソート(並び替え)に利用できるね!
時間間隔を計算
2つの日時の間隔を計算するには、Subtractメソッドを使用してTimeSpanを取得します。
DateTime startTime = new DateTime(2024, 12, 25, 10, 30, 0); // 2024年12月25日 10:30:00
DateTime endTime = new DateTime(2024, 12, 27, 15, 45, 30); // 2024年12月27日 15:45:30
TimeSpan duration = endTime.Subtract(startTime);
// または演算子を使用: TimeSpan duration = endTime - startTime;
Console.WriteLine($"{startTime}"); // 2024/12/25 10:30:00
Console.WriteLine($"{endTime}"); // 2024/12/27 15:45:30
Console.WriteLine($"{duration}"); // 2.05:15:30
Console.WriteLine($"{duration.TotalDays}"); // 2.219097222222222
Console.WriteLine($"{duration.TotalHours}"); // 53.25833333333333
Console.WriteLine($"{duration.TotalMinutes}");// 3195.5
TimeSpanは日数・時間・分・秒・ミリ秒まで詳細に時間間隔を表現できるんだね!
フォーマットと解析
日時を文字列として表示したり、文字列から日時を解析する操作は実用的なアプリで大事な機能です。
日時の文字列フォーマット
ToString()メソッドを使用して、様々な形式で日時を文字列に変換できます。
DateTime dt = new DateTime(2024, 12, 25, 10, 30, 0);
Console.WriteLine($"{dt.ToString("d")}"); // 2024/12/25
Console.WriteLine($"{dt.ToString("D")}"); // 2024年12月25日
Console.WriteLine($"{dt.ToString("t")}"); // 10:30
Console.WriteLine($"{dt.ToString("T")}"); // 10:30:00
Console.WriteLine($"{dt.ToString("F")}"); // 2024年12月25日 10:30:00
Console.WriteLine($"{dt.ToString("yyyy/MM/dd")}"); // 2024/12/25
Console.WriteLine($"{dt.ToString("HH:mm:ss")}"); // 10:30:00
Console.WriteLine($"{dt.ToString("yyyy年M月d日(ddd)")}"); // 2024年12月25日(水)
書式指定子(”d”、”D”、”t”、”T”、”F”など)は、日付や時刻を様々なパターン(短い日付、長い日付、短い時刻、長い時刻、完全な日付時刻など)で表示するためのものです。
カスタム書式(例:”yyyy/MM/dd” や “yyyy年M月d日(ddd)”)を使えば、年月日や曜日などを自由な形式で出力できます。
用途や表示先に応じて最適な日付・時刻表現を簡単に選択できますね。
DateTime.ToString()
で書式指定する以外にも、String.Format や 文字列補間($”…{dt:書式}…”`)でも、同じように書式指定を使えます。
String.Formatや文字列補間・文字列と書式設定などついては、以下の文字列に関する記事で解説しているので参考にしてください。

以下のように、JapaneseCalendar・CultureInfoと組み合わせ和暦(元号)を表示することも可能です。
// 和暦(元号)での表示
var japaneseCalendar = new System.Globalization.JapaneseCalendar();
var japaneseCulture = new System.Globalization.CultureInfo("ja-JP");
japaneseCulture.DateTimeFormat.Calendar = japaneseCalendar;
// 昭和の例
DateTime showaDate = new DateTime(1984, 12, 25, 10, 30, 0);
Console.WriteLine(
showaDate.ToString("ggyy年M月d日", japaneseCulture)); // 昭和59年12月25日
// 平成の例
DateTime heiseiDate = new DateTime(2018, 12, 25, 10, 30, 0);
Console.WriteLine(
heiseiDate.ToString("ggyy年M月d日", japaneseCulture)); // 平成30年12月25日
// 令和の例
DateTime reiwaDate = new DateTime(2024, 12, 25, 10, 30, 0);
Console.WriteLine(
reiwaDate.ToString("ggyy年M月d日", japaneseCulture)); // 令和06年12月25日
日時の書式一覧はMicrosoft Learnのドキュメント「標準の日時書式指定文字列」が参考になります。
文字列からの日時解析
文字列から日時を解析するには、ParseメソッドやTryParseメソッドを使用します。
// Parse(例外発生の可能性あり)
string dateString1 = "2024/12/25 10:30:00";
DateTime parsed1 = DateTime.Parse(dateString1);
Console.WriteLine($"解析結果: {parsed1}"); // 2024/12/25 10:30:00
// TryParse(安全な解析)
string dateString2 = "2024-12-25T10:30:00";
if (DateTime.TryParse(dateString2, out DateTime parsed2))
{
Console.WriteLine($"解析成功: {parsed2}"); // 2024/12/25 10:30:00
}
else
{
Console.WriteLine("解析失敗");
}
string offsetString = "2024-12-25T10:30:00+09:00";
if (DateTimeOffset.TryParse(offsetString, out DateTimeOffset offsetParsed))
{
Console.WriteLine($"解析成功: {offsetParsed}"); // 2024/12/25 10:30:00 +09:00
}
else
{
Console.WriteLine("解析失敗");
}
ユーザ入力を解析する場合などは、解析できない場合も想定し、TryParseを使って安全に処理するとよいでしょう。
厳密なフォーマット指定での解析
一般的な Parse
や TryParse
メソッドは、日付や時刻の表記が多少違っていても、多くの場合は自動的に解析してくれます。
しかし、「必ずこの形式だけ受け付けたい」といった場合は、ParseExact やTryParseExactを使います。
using System.Globalization;
string customFormat = "20241225_103000";
string formatPattern = "yyyyMMdd_HHmmss";
// TryParseExactは、「書式が完全一致したときだけ」パース成功するメソッド
if (DateTime.TryParseExact(
customFormat, // 解析したい文字列
formatPattern, // 書式(パターン文字列)
null, // カルチャ情報(nullならCurrentCulture)
DateTimeStyles.None, // 追加のパーススタイル(ここでは特に何もしない)
out DateTime exactParsed // 解析結果を受け取る変数
))
{
Console.WriteLine($"厳密解析成功: {exactParsed}"); // 2024/12/25 10:30:00
}
else
{
Console.WriteLine("解析失敗");
}
解析時の注意点
例えば「月/日/年」と「日/月/年」など、ローカル環境によって解釈がズレることがあります。フォーマットやカルチャを明示的に指定すると安全です。
システム間連携時はISO 8601形式を奨励します。“2024-12-25T10:30:00+09:00” のようなISO 8601書式なら、多くの言語・環境でズレなく扱えます。
日時を文字列データとして表現するとき、迷ったらISO 8601書式にしておけば間違いないでしょう。
タイムゾーンと夏時間の正しい扱い方
DateTimeOffsetの課題
DateTimeOffsetのオフセット情報は固定値のため、夏時間の切り替えを考慮した将来・過去の時間計算では正確性に欠ける場合があります。
例えば、ニューヨークの時間を6ヶ月後に計算する場合を考えてみましょう。
// オフセットだけの場合(問題のあるパターン)
DateTimeOffset standardTime =
new DateTimeOffset(2024, 2, 15, 10, 0, 0, TimeSpan.FromHours(-5)); // 標準時間
DateTimeOffset sixMonthsLater =
standardTime.AddMonths(6); // 2024/8/15 10:00:00 -05:00
Console.WriteLine($"6ヶ月後: {sixMonthsLater}");
// -05:00のまま(実際は夏時間で-04:00になるべき)
上記のコードでは、8月15日(夏時間)なのに標準時間のオフセット(-05:00)のままになってしまいます。本当はオフセット(-4:00)が正しいですね。
DateTimeOffsetってUTCからの時間差分の情報だけしか持ってないから、タイムゾーンを考慮した計算ができないってことだね…。
TimeZoneInfoを使った正しい処理
TimeZoneInfoクラスを使用することで、夏時間の切り替えを正確に扱えます。
// 正しいタイムゾーン処理
TimeZoneInfo easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
// 基準となる日時(UTC)を作成(2024年2月15日15:00:00 UTC)
DateTimeOffset baseUtc = new DateTimeOffset(2024, 2, 15, 15, 0, 0, TimeSpan.Zero);
// 6ヶ月後の日時を計算
DateTimeOffset futureUtc = baseUtc.AddMonths(6);
// TimeZoneInfoを使って東部時間に変換
// 2月は標準時間
DateTimeOffset standardTime = TimeZoneInfo.ConvertTime(baseUtc, easternTimeZone);
// 8月は夏時間
DateTimeOffset summerTime = TimeZoneInfo.ConvertTime(futureUtc, easternTimeZone);
Console.WriteLine($"2月の東部時間: {standardTime}"); // 2024/02/15 10:00:00 -05:00
Console.WriteLine($"8月の東部時間: {summerTime}"); // 2024/08/15 11:00:00 -04:00
//夏時間を判定
bool isStandardDst = easternTimeZone.IsDaylightSavingTime(standardTime);
bool isSummerDst = easternTimeZone.IsDaylightSavingTime(summerTime);
Console.WriteLine($"2月は夏時間: {isStandardDst}"); // false
Console.WriteLine($"8月は夏時間: {isSummerDst}"); // true
正確に計算できてるね!
これで、グローバル企業のスケジュール管理アプリなども作れますね!
DateOnly・TimeOnlyによる新しいアプローチ
.NET 6以降では、日付と時刻を分離して扱えるDateOnlyとTimeOnlyが導入されました。これらの型は、日付のみや時刻のみを扱いたい場合に最適です。
DateOnlyは日付のみを表現する型で、時刻情報を持ちません。
// DateOnlyの作成
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
DateOnly specificDate = new DateOnly(2024, 12, 25);
DateOnly fromString = DateOnly.Parse("2024-12-25");
Console.WriteLine($"今日: {today}"); // 2025/06/01
Console.WriteLine($"クリスマス: {specificDate}"); // 2024/12/25
Console.WriteLine($"文字列から: {fromString}"); // 2024/12/25
// 日付の操作
DateOnly nextWeek = today.AddDays(7);
DateOnly nextMonth = today.AddMonths(1);
DateOnly nextYear = today.AddYears(1);
Console.WriteLine($"来週: {nextWeek}"); // 2025/06/08
Console.WriteLine($"来月: {nextMonth}"); // 2025/07/01
Console.WriteLine($"来年: {nextYear}"); // 2026/06/01
TimeOnlyは時刻のみを表現する型で、日付情報を持ちません。
// TimeOnlyの作成
TimeOnly now = TimeOnly.FromDateTime(DateTime.Now);
TimeOnly specificTime = new TimeOnly(14, 30, 0);
TimeOnly fromString = TimeOnly.Parse("14:30:00");
Console.WriteLine($"現在時刻: {now}"); // 17:20:37(例)
Console.WriteLine($"指定時刻: {specificTime}"); // 14:30:00
Console.WriteLine($"文字列から: {fromString}"); // 14:30:00
// 時刻の操作
TimeOnly laterTime = specificTime.AddHours(2);
TimeOnly earlierTime = specificTime.AddMinutes(-30);
Console.WriteLine($"2時間後: {laterTime}"); // 16:30:00
Console.WriteLine($"30分前: {earlierTime}"); // 14:00:00
日付と時刻を分けて管理することで、コードがすっきりして理解しやすくなるね!
日にち単位のスケジュールや業務時間の管理など、日付と時刻を分けて考えたい場面で威力を発揮しますね。
TimeProviderの基本概念
.NET 8で導入されたTimeProviderは、時間に依存するコードのテストを容易にします。
従来は「現在時刻を使うコード」のテストが困難でしたが、TimeProvider
を使用することで時間を自由に設定できるようになりました。
なぜTimeProviderが必要なのか?
まず、従来の問題を見てみましょう。一週間のうち月曜だけ異なる処理を行うサービスのロジックを考えます。
// 問題のあるコード例
public class NewsService
{
public List<string> GetTodaysNews()
{
var today = DateTime.Now.Date; // ← 現在時刻に依存してしまう
// 今日のニュースを取得する処理
if (today.DayOfWeek == DayOfWeek.Monday)
{
return new List<string> { "通常ニュース", "月曜特集" };
}
return new List<string> { "通常ニュース" };
}
}
上記のコードは、実行した日によって結果が変わってしまうため、安定したテストが書けません。
テストを実行する日によって結果が変わっちゃうのは困るね…
TimeProviderを使った解決方法
TimeProvider
を使用することで、サービスロジックの時間を制御できるようになります。
// 改善されたコード
public class NewsService
{
private readonly TimeProvider _timeProvider;
// TimeProviderを外から受け取る
public NewsService(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public List<string> GetTodaysNews()
{
// TimeProviderから現在時刻を取得
var today = _timeProvider.GetLocalNow().Date;
if (today.DayOfWeek == DayOfWeek.Monday)
{
return new List<string> { "週刊ニュース", "月曜特集" };
}
return new List<string> { "通常ニュース" };
}
}
本番環境では以下のように使います。
// 本番環境では実際の時間を使用
var newsService = new NewsService(TimeProvider.System);
var news = newsService.GetTodaysNews();
Console.WriteLine(string.Join(", ", news));
テスト環境では以下のテストしたい日付で動作させます。
// 月曜日(2025年6月2日)を設定
var monday = new DateTimeOffset(2025, 6, 2, 10, 0, 0, TimeSpan.FromHours(9));
var fakeTimeProvider = new FakeTimeProvider(monday);
var newsService = new NewsService(fakeTimeProvider);
var news = newsService.GetTodaysNews();
// 月曜日なので週刊ニュースが含まれることを確認
...
FakeTimeProviderは以下のように定義します。
public class FakeTimeProvider : TimeProvider
{
private DateTimeOffset _fixedTime;
public FakeTimeProvider(DateTimeOffset fixedTime)
{
_fixedTime = fixedTime;
}
// 常に固定の時間を返す
public override DateTimeOffset GetUtcNow() => _fixedTime;
}
好きな時間を指定してサービスロジックの動作確認ができるってことはわかったけど、ちょっと難しいかも…。
時刻を取得する部分(GetUtcNowメソッド)の動作をポリモーフィズムで切り替えています。
オブジェクト指向(特にポリモーフィズムやインターフェイス)についても復習すると、より理解が深まるかもしれませんね。
ポリモーフィズム・インターフェイスについては以下の記事も参考にしてください。


まとめ
C#における日時処理の基本から最新機能まで幅広く解説しました。
日時処理は見た目以上に複雑で、タイムゾーンや夏時間などの要素が絡むため、適切な型の選択と正しい処理方法の理解が重要です。
C#で利用できる主な日時型として、汎用的なDateTime、UTC基準オフセット付きのDateTimeOffset、日付のみのDateOnly、時刻のみのTimeOnlyがあります。
また、タイムゾーンを扱うTimeZoneInfo、テスト支援のためのTimeProviderがあります。
特にグローバル展開するアプリでは、DateTimeOffsetとTimeZoneInfoを組み合わせることで、異なるタイムゾーンや夏時間の切り替えを正確に処理できます。
.NET 6以降のDateOnly・TimeOnlyや、.NET 8のTimeProviderなど、新しい機能も積極的に活用することで、より保守性の高いコードが書けるようになります。
日時処理は実用的なアプリ開発において避けて通れない重要な分野です。
引き続き、一緒に楽しくC#プログラミングを学んでいきましょう!