はじめに:「年月のみ比較」は“日付の細かさをあえて捨てるテクニック”
業務システムでは、
「2026年2月の売上」「2026年2月度の勤怠」「請求月が同じかどうか」
のように、“日付”ではなく“年月”だけを扱いたい場面がよくあります。
でも DateTime は「年・月・日・時・分・秒」まで全部持っているので、
そのまま比較すると「2026/02/01」と「2026/02/28」は“違う”と判定されてしまいます。
そこで必要になるのが、
「年月だけを取り出して比較する」「年月だけを表す型(または疑似的な型)を作る」
というユーティリティです。
ここでは、初心者向けに
DateTimeから年月だけを取り出して比較する方法- 「年月専用の小さな値オブジェクト」を作る方法
をかみ砕いて説明していきます。
基本形:DateTime から「年」と「月」だけを使って比較する
年と月だけを使って「同じ月かどうか」を判定する
一番シンプルなやり方は、DateTime の Year と Month プロパティだけを使って比較することです。
public static bool IsSameYearMonth(DateTime x, DateTime y)
{
return x.Year == y.Year && x.Month == y.Month;
}
C#使い方の例です。
DateTime a = new DateTime(2026, 2, 1);
DateTime b = new DateTime(2026, 2, 28);
DateTime c = new DateTime(2026, 3, 1);
Console.WriteLine(IsSameYearMonth(a, b)); // true(同じ 2026/02)
Console.WriteLine(IsSameYearMonth(a, c)); // false(2026/02 と 2026/03)
C#ここでの重要ポイントは、
「DateTime 同士をそのまま == で比べるのではなく、
“比較したい粒度(ここでは年と月)だけを取り出して比べる”」
という発想です。
「どちらが前の年月か」を比較する
「同じかどうか」だけでなく、
「どちらが前の年月か」「どちらが後か」を知りたいことも多いです。
その場合は、「年→月の順に比較する」というルールで書けます。
public static int CompareYearMonth(DateTime x, DateTime y)
{
int yearCompare = x.Year.CompareTo(y.Year);
if (yearCompare != 0)
return yearCompare;
return x.Month.CompareTo(y.Month);
}
C#使い方の例です。
DateTime a = new DateTime(2026, 2, 1);
DateTime b = new DateTime(2026, 3, 1);
int result = CompareYearMonth(a, b);
if (result < 0)
Console.WriteLine("a の年月が前");
else if (result > 0)
Console.WriteLine("a の年月が後");
else
Console.WriteLine("同じ年月");
C#ここでの重要ポイントは、
「年が違えば年だけで決着をつけ、年が同じなら月で決める」
という“辞書順”の考え方です。
これはソートや範囲判定にもそのまま使える、定番の比較パターンです。
応用:年月だけを表す小さな値オブジェクトを作る
YearMonth 構造体を自作する
年月だけを頻繁に扱うなら、DateTime を毎回 Year/Month で切り出すより、
「年月専用の型」を作ってしまったほうがコードがきれいになります。
public readonly struct YearMonth : IComparable<YearMonth>, IEquatable<YearMonth>
{
public int Year { get; }
public int Month { get; }
public YearMonth(int year, int month)
{
if (month < 1 || month > 12)
throw new ArgumentOutOfRangeException(nameof(month), "月は 1〜12 である必要があります。");
Year = year;
Month = month;
}
public static YearMonth FromDateTime(DateTime date)
=> new YearMonth(date.Year, date.Month);
public int CompareTo(YearMonth other)
{
int yearCompare = Year.CompareTo(other.Year);
if (yearCompare != 0)
return yearCompare;
return Month.CompareTo(other.Month);
}
public bool Equals(YearMonth other)
=> Year == other.Year && Month == other.Month;
public override bool Equals(object? obj)
=> obj is YearMonth ym && Equals(ym);
public override int GetHashCode()
=> HashCode.Combine(Year, Month);
public override string ToString()
=> $"{Year:D4}-{Month:D2}";
}
C#使い方の例です。
var ym1 = new YearMonth(2026, 2);
var ym2 = YearMonth.FromDateTime(new DateTime(2026, 2, 28));
var ym3 = new YearMonth(2026, 3);
Console.WriteLine(ym1.Equals(ym2)); // true
Console.WriteLine(ym1.CompareTo(ym3)); // 負の値(ym1 のほうが前)
Console.WriteLine(ym1); // 2026-02
C#ここでの重要ポイントは、
「年月という概念を、YearMonth という“名前のある型”にしてしまう」ことです。
これによって、
- 「このメソッドは年月単位で扱っている」という意図が明確になる
EqualsやCompareToを一度だけ正しく実装しておけば、以降は安心して使える
というメリットが生まれます。
年月の範囲判定・ループ処理に使う
「この年月が範囲内か?」を判定する
YearMonth を使うと、「年月の範囲」を扱うのも簡単になります。
public static bool IsWithin(YearMonth target, YearMonth from, YearMonth to)
{
return target.CompareTo(from) >= 0
&& target.CompareTo(to) <= 0;
}
C#使い方の例です。
var from = new YearMonth(2026, 1);
var to = new YearMonth(2026, 12);
Console.WriteLine(IsWithin(new YearMonth(2026, 5), from, to)); // true
Console.WriteLine(IsWithin(new YearMonth(2025, 12), from, to)); // false
C#「年月ごとにループする」
売上集計や勤怠集計などで、
「開始年月から終了年月まで、1ヶ月ずつ進めながら処理する」
というパターンもよく出てきます。
public static IEnumerable<YearMonth> EnumerateYearMonths(YearMonth from, YearMonth to)
{
if (from.CompareTo(to) > 0)
yield break;
int year = from.Year;
int month = from.Month;
while (true)
{
var current = new YearMonth(year, month);
if (current.CompareTo(to) > 0)
yield break;
yield return current;
month++;
if (month > 12)
{
month = 1;
year++;
}
}
}
C#使い方の例です。
var from = new YearMonth(2026, 1);
var to = new YearMonth(2026, 3);
foreach (var ym in EnumerateYearMonths(from, to))
{
Console.WriteLine(ym); // 2026-01, 2026-02, 2026-03
}
C#ここでの重要ポイントは、
「年月の加算(1ヶ月進める)も、自分でルールを決めて実装している」ことです。DateTime.AddMonths を使ってもよいですが、YearMonth の中で完結させると、年月の扱いが一箇所に集約されてスッキリします。
実務で意識してほしいポイント
「日付まで見るのか、年月まででいいのか」を最初に決める
売上・請求・勤怠などの集計では、
「日単位」「月単位」「年単位」が混ざると、
バグの原因になりやすいです。
最初に、
- この画面・この機能は「年月単位」で扱う
- 日付はあくまで補助情報
と決めてしまい、コード上も YearMonth のような型で表現しておくと、
「うっかり日付まで比較してしまった」という事故を防げます。
DB とのやり取りも「年月の粒度」を意識する
DB に DATE や DATETIME で保存している場合でも、
アプリ側では「年月だけを使う」と決めておけば、
- 取得した
DateTimeからYearMonthを作る - 検索条件も「年」と「月」で絞る
といった設計ができます。
まとめ:「年月のみ比較ユーティリティ」は“必要な粒度だけを見るためのレンズ”
年月のみ比較の本質は、
「DateTime が持っている細かすぎる情報(日・時刻)をあえて捨てて、
“年と月だけ”という粒度で世界を見るためのレンズを用意すること」です。
押さえておきたいポイントを言葉でまとめると、こうなります。
DateTime同士をそのまま比較せず、YearとMonthだけを取り出して比較する。- 「同じ年月か」「どちらが前か」は、年→月の順に比較するシンプルなロジックで書ける。
- よく使うなら
YearMonthのような値オブジェクトを作り、比較・表示・範囲判定をそこに集約する。 - 集計や検索の単位が「年月」なら、コード上も「年月専用の型」で表現しておくとバグが減る。
ここまで押さえれば、
「なんとなく DateTime をそのまま使っている」状態から抜け出して、
“業務・実務で本当に必要な粒度だけを扱う、意図のある日付・時間処理”が書けるようになります。
