C#を用いたシステム開発において、年月日の扱いは避けては通れない非常に重要なテーマです。

業務アプリケーションからWeb APIの開発にいたるまで、日付データの取得や計算、表示形式の制御は日常的に行われます。

近年のC#および.NETの進化により、日付を扱うための選択肢は広がり、より型安全で意図の明確な実装が可能になりました。

特に.NET 6以降で導入されたDateOnly構造体は、従来のDateTime構造体との使い分けにおいて重要な役割を担っています。

本記事では、C#における年月日の取得方法や操作の基本から、最新の推奨される使い分けまでを詳しく解説します。

C#における日付処理の基本概念

C#で日付を扱う際、まず理解しておくべきなのがDateTimeDateOnlyの2つの主要な構造体です。

これまでは、時間情報を含むDateTimeが唯一の選択肢として長く使われてきました。

しかし、現代の開発では、誕生日や記念日のように「時刻を必要としない純粋な日付」を扱うケースが増えています。

DateTime構造体の役割と特徴

DateTimeは、日付と時刻の両方を保持するための構造体であり、C#の誕生初期から存在します。

0001年1月1日午前12:00:00から9999年12月31日午後11:59:59までの範囲をカバーしています。

時刻情報まで含めて管理したい場合や、データベースのdatetime型と連携する場合には、依然としてメインで利用されます。

内部的にはタイマーのカウントのようなTicks(100ナノ秒単位)で数値を保持しており、精密な時間計測も可能です。

DateOnly構造体の登場背景

DateOnlyは.NET 6から追加された、「日付のみ」を保持することに特化した構造体です。

従来のDateTimeでは、日付だけを扱いたい場合でも内部的に「00:00:00」という時刻情報が保持されていました。

これにより、タイムゾーンの影響で日付が意図せず前後にずれてしまうといったバグが発生しやすい課題がありました。

DateOnlyを使用することで、時間やタイムゾーンに依存しない純粋な年月日だけのデータを安全に扱うことができます。

現在の年月日を取得する方法

プログラムの中で「今日」の日付を取得することは、ログの出力や有効期限のチェックなどで頻繁に発生します。

C#では、それぞれの構造体に対して直感的なプロパティが用意されています。

DateTimeによる現在日時の取得

DateTimeを使用して現在の日付や時刻を取得する場合、以下のコードを使用します。

C#
// 現在のシステム日時(時刻含む)を取得
DateTime now = DateTime.Now;

// 現在のシステム日付(時刻は00:00:00)を取得
DateTime today = DateTime.Today;

Console.WriteLine($"現在の日時: {now}");
Console.WriteLine($"今日の日付: {today}");
実行結果
現在の日時: 2026/05/08 18:30:45
今日の日付: 2026/05/08 00:00:00

DateTime.Nowは実行環境のローカルタイムを取得し、DateTime.UtcNowは協定世界時を取得します。

DateTime.Todayは時刻部分がすべてゼロにリセットされた状態で返されるため、日付比較に適しています。

DateOnlyで今日の日付を取得する

DateOnlyを使用して、時刻を含まない今日の日付を取得する方法は以下の通りです。

C#
// 現在のローカル日付のみを取得
DateOnly todayOnly = DateOnly.FromDateTime(DateTime.Now);

Console.WriteLine($"DateOnlyでの今日: {todayOnly}");
実行結果
DateOnlyでの今日: 2026/05/08

DateOnly.FromDateTimeメソッドを使用することで、既存のDateTimeから日付部分だけを抽出できます。

これにより、データ構造として「時間は持っていない」ことが保証され、コードの可読性が向上します。

年月日の書式設定と文字列変換

取得した日付データを画面に表示したり、ファイル名に使用したりする際、特定のフォーマットに変換する必要があります。

C#のToStringメソッドは非常に強力で、多様な書式指定子をサポートしています。

ToStringメソッドによるカスタムフォーマット

年月日を「2026年05月08日」や「2026-05-08」といった形式で出力する例を紹介します。

C#
DateTime dt = new DateTime(2026, 5, 8);

// 標準的なスラッシュ区切り
string format1 = dt.ToString("yyyy/MM/dd");

// 日本語の単位を含める
string format2 = dt.ToString("yyyy年MM月dd日");

// 2桁の年と月日
string format3 = dt.ToString("yy-MM-dd");

Console.WriteLine(format1);
Console.WriteLine(format2);
Console.WriteLine(format3);
実行結果
2026/05/08
2026年05月08日
26-05-08

yyyyは4桁の年、MMは2桁の月、ddは2桁の日を表します。

Mdのように1文字で指定すると、1桁の場合に先頭のゼロを表示しない形式になります。

カルチャ指定による国際化対応

アプリケーションを海外展開する場合、国や地域によって日付の表記順序が異なることに注意が必要です。

特定の言語設定に基づいた変換を行うには、System.Globalization.CultureInfoを使用します。

C#
using System.Globalization;

DateTime dt = new DateTime(2026, 5, 8);

// 米国のカルチャ (月/日/年)
string usDate = dt.ToString("d", new CultureInfo("en-US"));

// 日本のカルチャ (年/月/日)
string jpDate = dt.ToString("d", new CultureInfo("ja-JP"));

Console.WriteLine($"US: {usDate}");
Console.WriteLine($"JP: {jpDate}");
実行結果
US: 5/8/2026
JP: 2026/05/08

このように、カルチャを明示的に指定することで、実行環境に依存しない出力を保証できます。

年月日の計算と操作

日付の「3日後」を求めたり、二つの日付の間の日数を計算したりする操作は、業務ロジックで多用されます。

日付の加算・減算

DateTimeDateOnlyには、日付を操作するための便利なメソッドが用意されています。

C#
DateOnly startDay = new DateOnly(2026, 5, 8);

// 10日後を取得
DateOnly after10Days = startDay.AddDays(10);

// 2ヶ月前を取得
DateOnly before2Months = startDay.AddMonths(-2);

// 1年後を取得
DateOnly after1Year = startDay.AddYears(1);

Console.WriteLine($"開始日: {startDay}");
Console.WriteLine($"10日後: {after10Days}");
Console.WriteLine($"2ヶ月前: {before2Months}");
Console.WriteLine($"1年後: {after1Year}");
実行結果
開始日: 2026/05/08
10日後: 2026/05/18
2ヶ月前: 2026/03/08
1年後: 2027/05/08

これらのメソッドは、元のオブジェクトを変更せずに新しいインスタンスを返します(イミュータブル)。

そのため、startDay.AddDays(10);と書くだけでは元の変数は変わらない点に注意してください。

2つの日付の差分を求める

二つの日付の間に何日あるかを計算するには、引き算を行います。

DateTime同士の引き算の結果は、TimeSpan型として返されます。

C#
DateTime date1 = new DateTime(2026, 12, 25);
DateTime date2 = new DateTime(2026, 5, 8);

TimeSpan diff = date1 - date2;

Console.WriteLine($"クリスマスまであと {diff.Days} 日です。");
実行結果
クリスマスまであと 231 日です。

DateOnlyの場合は直接引き算ができないため、内部のTicksを利用するか、一度DateTimeに変換して計算します。

文字列から日付への変換(パース)

ユーザーが入力したテキストや、CSVファイルから読み込んだ文字列を日付型に変換する処理です。

入力形式が正しくない場合に備えて、安全な変換方法を選択する必要があります。

ParseとTryParseの使い分け

変換にはParseメソッドとTryParseメソッドの2種類があります。

Parseは変換に失敗すると例外(エラー)を投げますが、TryParseは成否を返り値で示します。

C#
string input = "2026/05/08";

// TryParseによる安全な変換
if (DateOnly.TryParse(input, out DateOnly result))
{
    Console.WriteLine($"変換成功: {result}");
}
else
{
    Console.WriteLine("無効な日付形式です。");
}

不特定多数のユーザーが入力する値に対しては、必ずTryParseを使用するのがベストプラクティスです。

厳密な形式でのパース

「20260508」のように区切り文字がない特殊な形式を読み込む場合は、ParseExactを使用します。

C#
string s = "20260508";
DateOnly d = DateOnly.ParseExact(s, "yyyyMMdd", CultureInfo.InvariantCulture);

Console.WriteLine($"厳密パース結果: {d}");

これにより、意図しない形式の混入を防ぎ、データの整合性を保つことができます。

DateTimeとDateOnlyの使い分け基準

どちらの構造体を使うべきか迷った際は、以下のガイドラインを参考にしてください。

項目DateTimeDateOnly
保持する情報日付 + 時刻 + 種類(Local/Utc)日付のみ
主な用途システムログ、更新日時、予約時間誕生日、契約日、祝日設定
タイムゾーン影響受ける(変換が必要な場合がある)受けない(どこでも同じ日付)
メモリ効率標準より軽量

基本的には、「時間に依存しないデータ」であればDateOnlyを選択するのが現代的な設計です。

一方で、既存の古いライブラリやフレームワークがDateTimeしかサポートしていない場合は、無理に変換せずDateTimeを使用する柔軟さも必要です。

データベースやJSONとの連携

プログラム内部だけでなく、データの保存や通信における扱いも重要です。

Entity Framework Coreでの扱い

最新のEntity Framework Core(EF Core)では、SQL Serverなどのデータベースにあるdate型を、自動的にC#のDateOnlyにマッピングできます。

これにより、DB設計とコードの型を完全に一致させることが可能になりました。

以前のように「DBはdate型なのに、C#側ではDateTimeで受け取って時間を無視する」といった不自然なコードは不要です。

System.Text.Jsonでのシリアライズ

Web APIで日付データをやり取りする際、標準のJSONシリアライザもDateOnlyに対応しています。

デフォルトでは「”2026-05-08″」というISO 8601形式の文字列として出力されます。

これはJavaScript側でも扱いやすい形式であるため、フロントエンドとの連携も非常にスムーズです。

まとめ

C#における年月日の扱いは、DateOnlyの登場によってよりシンプルかつ堅牢になりました。

時刻情報が必要なログや履歴管理にはDateTimeを使用し、誕生日や予定日のような日付単体の管理にはDateOnlyを活用しましょう。

また、ユーザーへの表示や外部システムとの連携では、書式指定子やカルチャ設定を適切に行うことがトラブルを防ぐ鍵となります。

本記事で紹介したメソッドやプロパティを組み合わせることで、複雑な日付計算や変換もミスなく実装できるはずです。

常に「そのデータに時刻が必要か」を問い直し、最適な型を選択する習慣を身につけてください。