C# Tips | 日付・時間処理:年月のみ比較

C# C#
スポンサーリンク

はじめに:「年月のみ比較」は“日付の細かさをあえて捨てるテクニック”

業務システムでは、
「2026年2月の売上」「2026年2月度の勤怠」「請求月が同じかどうか」
のように、“日付”ではなく“年月”だけを扱いたい場面がよくあります。

でも DateTime は「年・月・日・時・分・秒」まで全部持っているので、
そのまま比較すると「2026/02/01」と「2026/02/28」は“違う”と判定されてしまいます。

そこで必要になるのが、
「年月だけを取り出して比較する」「年月だけを表す型(または疑似的な型)を作る」
というユーティリティです。

ここでは、初心者向けに

  • DateTime から年月だけを取り出して比較する方法
  • 「年月専用の小さな値オブジェクト」を作る方法

をかみ砕いて説明していきます。


基本形:DateTime から「年」と「月」だけを使って比較する

年と月だけを使って「同じ月かどうか」を判定する

一番シンプルなやり方は、
DateTimeYearMonth プロパティだけを使って比較することです。

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 という“名前のある型”にしてしまう」ことです。
これによって、

  • 「このメソッドは年月単位で扱っている」という意図が明確になる
  • EqualsCompareTo を一度だけ正しく実装しておけば、以降は安心して使える

というメリットが生まれます。


年月の範囲判定・ループ処理に使う

「この年月が範囲内か?」を判定する

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 に DATEDATETIME で保存している場合でも、
アプリ側では「年月だけを使う」と決めておけば、

  • 取得した DateTime から YearMonth を作る
  • 検索条件も「年」と「月」で絞る

といった設計ができます。


まとめ:「年月のみ比較ユーティリティ」は“必要な粒度だけを見るためのレンズ”

年月のみ比較の本質は、
DateTime が持っている細かすぎる情報(日・時刻)をあえて捨てて、
“年と月だけ”という粒度で世界を見るためのレンズを用意すること」です。

押さえておきたいポイントを言葉でまとめると、こうなります。

  • DateTime 同士をそのまま比較せず、YearMonth だけを取り出して比較する。
  • 「同じ年月か」「どちらが前か」は、年→月の順に比較するシンプルなロジックで書ける。
  • よく使うなら YearMonth のような値オブジェクトを作り、比較・表示・範囲判定をそこに集約する。
  • 集計や検索の単位が「年月」なら、コード上も「年月専用の型」で表現しておくとバグが減る。

ここまで押さえれば、
「なんとなく DateTime をそのまま使っている」状態から抜け出して、
“業務・実務で本当に必要な粒度だけを扱う、意図のある日付・時間処理”が書けるようになります。

C#C#
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました