C#を用いたシステム開発において、日付操作は非常に頻繁に発生する処理の一つです。
特に業務システムや会計システムでは、請求処理や月次レポートの作成など、「月末日」を正確に判定・取得するロジックが欠かせません。
一見単純な処理に思えますが、うるう年の判定や、月によって異なる日数(28日、30日、31日)を考慮する必要があります。
本記事では、C#の標準ライブラリを活用して、月末日を効率的かつ正確に取得する方法について、基礎から応用まで詳しく紹介します。
最新の.NET環境で推奨される手法についても触れていきますので、開発現場での実装に役立ててください。
C#で月末日を取得する標準的な手法
C#で月末の日付を特定するために最も一般的に使用されるのが、System.DateTime.DaysInMonthメソッドです。
このメソッドは、指定された年と月に含まれる日数を整数値(int)で返してくれます。
うるう年などの複雑な計算も、内部的にカレンダーロジックに基づいて処理されるため、開発者が自身で条件分岐を書く必要はありません。
DateTime.DaysInMonthの基本的な使い方
まずは、特定の年月の最終日が何日であるかを取得する基本的なコードを見てみましょう。
// 2026年2月の末日を取得する
int year = 2026;
int month = 2;
int daysInMonth = DateTime.DaysInMonth(year, month);
Console.WriteLine($"{year}年{month}月の末日は{daysInMonth}日です。");
2026年2月の末日は28日です。
このように、年と月を引数に渡すだけで、その月の最大日数を取得することが可能です。
2026年は平年であるため、実行結果として28が返されていることがわかります。
DateTimeオブジェクトとして月末日を生成する
数値としての「日数」ではなく、具体的な日付型(DateTime)として月末データを扱いたい場合は、以下のように記述します。
int year = 2026;
int month = 12;
// DateTime.DaysInMonthで取得した数値をDayプロパティに指定する
DateTime lastDay = new DateTime(year, month, DateTime.DaysInMonth(year, month));
Console.WriteLine($"2026年12月の最終日: {lastDay.ToString("yyyy/MM/dd")}");
2026年12月の最終日: 2026/12/31
この手法は、最も直感的でミスが少ない実装方法として推奨されます。
特定の日付からその月の月末日を求める応用テクニック
開発の現場では、あらかじめ年と月が分かっているケースだけでなく、「今日」や「入力された任意の日付」を基準に月末を算出したいケースが多くあります。
その場合に便利なアプローチをいくつか紹介します。
翌月の1日から1日差し引く方法
DateTime.DaysInMonthを使わずに、日付操作のメソッドだけで完結させることも可能です。
「翌月の1日」を求め、そこからマイナス1日することで、確実に当月の末日へ辿り着くことができます。
DateTime targetDate = new DateTime(2026, 4, 15);
// 対象日の翌月1日を求める
DateTime firstDayOfNextMonth = new DateTime(targetDate.Year, targetDate.Month, 1).AddMonths(1);
// 1日引くことで今月末日を取得
DateTime lastDayOfThisMonth = firstDayOfNextMonth.AddDays(-1);
Console.WriteLine($"基準日: {targetDate.ToShortDateString()}");
Console.WriteLine($"今月末日: {lastDayOfThisMonth.ToShortDateString()}");
基準日: 2026/04/15
今月末日: 2026/04/30
この方法は、日付計算のロジックを連結させるだけで済むため、シンプルに記述できるというメリットがあります。
現代的なC#におけるDateOnly型の活用
.NET 6以降では、時刻情報を含まない日付専用の型であるDateOnlyが導入されました。
データベースの「DATE型」とのマッピングや、時刻が不要なビジネスロジックにおいては、DateOnlyを使用することがモダンな書き方とされています。
DateOnlyで月末日を取得するコード
DateOnlyにはDateTime.DaysInMonthに相当する静的メソッドは直接ありませんが、既存のDateTimeクラスの機能と組み合わせて利用します。
int year = 2026;
int month = 6;
// DateOnlyの月末日を作成
DateOnly lastDay = new DateOnly(year, month, DateTime.DaysInMonth(year, month));
Console.WriteLine($"DateOnlyでの月末日: {lastDay}");
DateOnlyでの月末日: 2026/06/30
請求日や契約終了日など、時刻を扱う必要がない項目では、DateTimeではなくDateOnlyを積極的に活用しましょう。
不要な時分秒の考慮を排除することで、比較演算のミスを防ぎ、コードの意図が明確になるという効果が得られます。
任意の日付が「月末日かどうか」を判定する
取得するだけでなく、渡された日付がその月の最終日であるかをチェックする処理も重要です。
例えば、給与計算などで「月末のみ実行する」というバリデーションが必要な場合に活用できます。
判定ロジックの実装例
判定は、その日付のDayプロパティと、その年月のDaysInMonthの結果を比較するだけで完了します。
public bool IsLastDayOfMonth(DateTime date)
{
// その日付の日が、その月の最大日数と一致するかを確認
return date.Day == DateTime.DaysInMonth(date.Year, date.Month);
}
// テスト
DateTime testDate1 = new DateTime(2026, 2, 28);
DateTime testDate2 = new DateTime(2026, 3, 30);
Console.WriteLine($"{testDate1.ToShortDateString()} は月末? : {IsLastDayOfMonth(testDate1)}");
Console.WriteLine($"{testDate2.ToShortDateString()} は月末? : {IsLastDayOfMonth(testDate2)}");
2026/02/28 は月末? : True
2026/03/30 は月末? : False
このように、関数化しておくことで可読性が高まります。
拡張メソッドで月末処理を共通化する
プロジェクト全体で頻繁に月末取得を行う場合は、拡張メソッドを定義するのが最も効率的です。
拡張メソッドを使用することで、既存の型に対して独自のメソッドを追加したかのような記述が可能になります。
DateTime型を拡張する
public static class DateTimeExtensions
{
/// <summary>
/// 対象日付の月末日を取得します。
/// </summary>
public static DateTime ToLastDayOfMonth(this DateTime date)
{
int days = DateTime.DaysInMonth(date.Year, date.Month);
return new DateTime(date.Year, date.Month, days, date.Hour, date.Minute, date.Second, date.Millisecond);
}
}
// 利用シーン
DateTime now = DateTime.Now;
DateTime lastDay = now.ToLastDayOfMonth();
このように定義しておけば、date.ToLastDayOfMonth()という短い形式で呼び出すことができ、コードが格段にスッキリします。
月末計算における注意点
C#で日付を扱う際には、いくつか注意すべきポイントがあります。
以下の表に、月末処理に関連する主要なメソッドと考慮事項をまとめました。
| 手法 | メリット | デメリット・注意点 |
|---|---|---|
DateTime.DaysInMonth | 最も標準的で高速。可読性が高い。 | 返り値が int なので DateTime への再変換が必要。 |
AddMonths(1).AddDays(-1) | 日付計算ロジックだけで完結する。 | 時間の扱い(0時0分になる等)に注意が必要。 |
DateOnly型の利用 | 時刻を考慮せず日付のみ安全に扱える。 | 古い.NET Framework環境では使用できない。 |
特に「時刻」の扱いには注意してください。
new DateTime(year, month, lastDay)で新しくインスタンスを生成した場合、時刻はデフォルトで「00:00:00」になります。
もし元のデータが持っていた時刻情報を維持したい場合は、拡張メソッドの例で示したように、時刻プロパティを個別に渡す必要があります。
うるう年の扱い
C#のDateTime.DaysInMonthは、内部でうるう年のアルゴリズムを完全に実装しています。
具体的には、「4で割り切れる年はうるう年、ただし100で割り切れる年は平年、しかし400で割り切れる年はうるう年」というルールに従います。
自前で if (year % 4 == 0) といった判定を書くと、100年単位や400年単位の例外でバグを発生させるリスクがあるため、必ず標準メソッドを使用するようにしましょう。
まとめ
C#における月末日の取得と判定は、標準のDateTime.DaysInMonthメソッドを活用することで、正確かつ簡潔に実装できます。
最新の.NET開発であれば、用途に応じてDateOnly型を使い分けることで、より堅牢なプログラムを作成することが可能です。
本記事で紹介したように、頻繁に使用する処理は拡張メソッドとして共通化し、チーム全体で再利用しやすい形で定義しておくことをおすすめします。
月末処理は一見地味ですが、システムの信頼性を支える重要なロジックです。
今回学んだテクニックを活かして、バグのない日付処理を実装していきましょう。
