はじめに:「月跨ぎ判定」は“どの月のデータとして扱うか”を決めるための鍵
業務システムでは、
「勤務が 1/31 22:00〜2/1 06:00 のとき、どの月の勤務として集計するか」
「売上期間が 3/25〜4/5 のとき、月次レポートをどう分けるか」
のように、“期間が月をまたいでいるかどうか”がとても重要になります。
ここでいう「月跨ぎ判定」は、
「2つの日付(または日時)が同じ月の中に収まっているか」
「それとも別の月にまたがっているか」
を判定するための小さなユーティリティです。
まずは一番シンプルな「日付同士の月跨ぎ判定」から入り、
そのあと「日時(DateTime)」「勤務時間のような期間」への応用までかみ砕いて説明します。
基本形:2つの日付が“同じ年月かどうか”で判定する
発想:「月が変わっているかどうか」だけを見る
月跨ぎかどうかを判定する一番シンプルな考え方は、
「開始日と終了日の“年と月”が同じかどうか」を見ることです。
年と月が同じなら「月跨ぎではない」
年か月のどちらかが違えば「月跨ぎである」
というルールにできます。
public static bool IsCrossingMonth(DateTime start, DateTime end)
{
return start.Year != end.Year || start.Month != end.Month;
}
C#このメソッドは「月をまたいでいるか?」を判定するので、
戻り値が true なら「月跨ぎ」、false なら「同じ月の中」と読みます。
例:月跨ぎかどうかを試してみる
DateTime a1 = new DateTime(2026, 1, 10);
DateTime a2 = new DateTime(2026, 1, 31);
DateTime b1 = new DateTime(2026, 1, 31);
DateTime b2 = new DateTime(2026, 2, 1);
Console.WriteLine(IsCrossingMonth(a1, a2)); // false(1月の中で完結)
Console.WriteLine(IsCrossingMonth(b1, b2)); // true(1月→2月にまたいでいる)
C#ここでの重要ポイントは、
「日付の“大小”ではなく、“年と月が変わっているかどうか”だけを見ている」ことです。
これだけで、月跨ぎかどうかの判定はほぼ足ります。
応用:DateTime(時刻付き)でも考え方は同じ
時刻がついていても「Year と Month だけを見る」
勤務時間などでは、DateTime に時刻も含まれますが、
月跨ぎ判定のロジック自体はまったく同じです。
public static bool IsCrossingMonth(DateTimeOffset start, DateTimeOffset end)
{
return start.Year != end.Year || start.Month != end.Month;
}
C#DateTime でも DateTimeOffset でも、
「どのタイムゾーンで見るか」を決めたうえで、Year と Month だけを比較すればOKです。
例:夜勤の勤務時間での月跨ぎ
DateTime shiftStart = new DateTime(2026, 1, 31, 22, 0, 0); // 1/31 22:00
DateTime shiftEnd = new DateTime(2026, 2, 1, 6, 0, 0); // 2/1 06:00
Console.WriteLine(IsCrossingMonth(shiftStart, shiftEnd)); // true
C#ここでの重要ポイントは、
「時刻が何時であっても、“月が変わっているかどうか”という観点では Year/Month だけ見ればよい」
という割り切りです。
「月跨ぎしていない」ことを前提にした処理もよく書く
「同じ月の中に収まっているか?」を判定する
逆に、「この期間は同じ月の中で完結していることが前提」という処理も多いです。
その場合は、「月跨ぎしていないか?」をチェックするユーティリティを用意しておくと安全です。
public static bool IsWithinSameMonth(DateTime start, DateTime end)
{
return start.Year == end.Year && start.Month == end.Month;
}
C#例として、「同じ月の中でしか登録してはいけない期間入力」のバリデーションを考えます。
if (!IsWithinSameMonth(start, end))
{
Console.WriteLine("期間は同じ月の中で指定してください。");
}
C#ここでの重要ポイントは、
「月跨ぎ判定」と「同じ月かどうか判定」は表裏一体で、
どちらも Year/Month の比較だけで書ける、ということです。
もう一歩:月跨ぎした場合に“どの月に属するか”を決める
夜勤などで「どっちの月の勤務とみなすか」を決める
実務では、「月跨ぎかどうか」だけでなく、
「月跨ぎしたとき、どの月のデータとして扱うか」を決める必要があります。
例えば夜勤の場合、
開始日の月に属する(1/31 22:00〜2/1 6:00 を“1月分”とみなす)
終了日の月に属する(同じ勤務を“2月分”とみなす)
など、会社ごと・システムごとにルールが違います。
これもユーティリティにしておくと分かりやすくなります。
public enum MonthBelongRule
{
StartMonth,
EndMonth
}
public static (int Year, int Month) GetBelongYearMonth(
DateTime start,
DateTime end,
MonthBelongRule rule)
{
return rule switch
{
MonthBelongRule.StartMonth => (start.Year, start.Month),
MonthBelongRule.EndMonth => (end.Year, end.Month),
_ => throw new ArgumentOutOfRangeException(nameof(rule))
};
}
C#例として、夜勤の勤務を「開始月に属する」とみなす場合です。
DateTime shiftStart = new DateTime(2026, 1, 31, 22, 0, 0);
DateTime shiftEnd = new DateTime(2026, 2, 1, 6, 0, 0);
var belong = GetBelongYearMonth(shiftStart, shiftEnd, MonthBelongRule.StartMonth);
Console.WriteLine($"{belong.Year}/{belong.Month:D2}"); // 2026/01
C#ここでの重要ポイントは、
「月跨ぎ判定」と「どの月に属するかのルール」はセットで考えると設計がきれいになる、ということです。
判定だけで終わらせず、「属する月」を決めるところまでユーティリティ化しておくと、集計処理が書きやすくなります。
実務で意識してほしいポイント
「日付の大小」と「月跨ぎ」は別物として考える
start <= end かどうかのチェック(期間として正しいか)と、
「月をまたいでいるかどうか」のチェックは、目的が違います。
期間として正しいか → 開始日と終了日の前後関係
月跨ぎかどうか → 年と月が変わっているかどうか
この2つを混ぜずに、別々のユーティリティとして用意しておくと、
バリデーションロジックがとても読みやすくなります。
「年月単位で集計する処理」と相性が良い
月跨ぎ判定は、
「年月のみ比較」「YearMonth 型」などと組み合わせるとさらに威力を発揮します。
例えば、
開始・終了から YearMonth を作る
同じ YearMonth なら「月跨ぎなし」、違えば「月跨ぎあり」
という形にすると、年月単位の集計処理と自然につながります。
まとめ:「月跨ぎ判定ユーティリティ」は“月次の境界線をはっきりさせる道具”
月跨ぎ判定の本質は、
「この期間は一つの月の中で完結しているのか、それとも月をまたいでいるのか」
という問いに、シンプルかつ一貫した答えを出すことです。
押さえておきたいポイントを言葉でまとめると、こうなります。
開始と終了の Year と Month を比較し、違っていれば「月跨ぎ」と判定できる。
「月跨ぎしていないこと」を前提にした処理では、先に IsWithinSameMonth でチェックしておくと安全。
夜勤など「月跨ぎが前提」のケースでは、「どの月に属するか」のルールもユーティリティとして決めておく。
期間の前後関係チェックと、月跨ぎ判定は目的が違うので、別メソッドとして分けておく。
ここまで理解できれば、
「なんとなく DateTime を比較している」状態から一歩進んで、
“月次集計や勤怠計算で迷わない、意図のある日付・時間ユーティリティ”を書けるようになります。
