はじめに 「日付範囲判定」は“ビジネスルールの線引き”そのもの
「この日付はキャンペーン期間内か?」「契約有効期間に含まれるか?」「集計対象期間か?」
こういう“期間に入っているかどうか”の判定は、業務システムで必ず出てきます。
C# では、DateTime をそのまま比較すれば範囲判定は書けますが、
「開始・終了を含むか」「日付だけか、時刻も含めるか」を意識しないと、境界でズレたバグが出やすいところです。
ここでは、
基本の範囲判定パターン(閉区間/半開区間)、日付だけの判定、ユーティリティ化、
そして実務でハマりやすい落とし穴まで、初心者向けにかみ砕いて説明します。
基本の考え方:範囲判定は「開始」「終了」「含むかどうか」の組み合わせ
一番基本の形「from 以上、to 以下」(閉区間)
まずは、開始日と終了日を両方含む「閉区間」のパターンです。
「2026/02/10〜2026/02/20 の間に入っているか?」を判定するイメージです。
public static bool IsBetweenInclusive(DateTime target, DateTime from, DateTime to)
{
return from <= target && target <= to;
}
C#使い方の例です。
DateTime from = new DateTime(2026, 2, 10, 0, 0, 0);
DateTime to = new DateTime(2026, 2, 20, 23, 59, 59);
DateTime target = new DateTime(2026, 2, 20, 12, 0, 0);
bool inRange = IsBetweenInclusive(target, from, to); // true
C#ここでの重要ポイントは、「開始も終了も含む」というルールを、<= と >= で明確に表現していることです。
業務仕様で「最終日も含む」と書いてあるなら、この形が素直です。
終了を含まない「半開区間」パターン
期間を扱うときによく使われるもう一つの形が、「終了を含まない」半開区間です。
「from 以上、to 未満」という形になります。
public static bool IsBetweenHalfOpen(DateTime target, DateTime from, DateTime to)
{
return from <= target && target < to;
}
C#例えば、「2026/02/10 0:00 から 2026/02/20 0:00 まで」という扱いにしておくと、
日付をまたぐ連続した期間をつなげても、重なりや抜けが出ません。
この「終了を含まない」ルールは、
時間帯や連続期間を扱うときにとても相性が良いので、
「時間も含めた範囲」を扱うときは半開区間を採用することが多いです。
日付だけの範囲判定:「時刻を無視して日付で比べる」
.Date を使って「日付同士」にそろえる
「2026/02/10〜2026/02/20 の“日付としての期間”に入っているか?」
というように、時刻は関係なく日付だけで判定したいケースも多いです。
その場合は、DateTime.Date を使って、日付だけにそろえてから比較します。
public static bool IsDateBetweenInclusive(DateTime target, DateTime from, DateTime to)
{
DateTime d = target.Date;
DateTime df = from.Date;
DateTime dt = to.Date;
return df <= d && d <= dt;
}
C#使い方の例です。
DateTime from = new DateTime(2026, 2, 10, 0, 0, 0);
DateTime to = new DateTime(2026, 2, 20, 0, 0, 0);
DateTime target = new DateTime(2026, 2, 20, 23, 59, 59);
bool inRange = IsDateBetweenInclusive(target, from, to); // true
C#target は 2/20 の 23:59:59 ですが、.Date で 2/20 0:00 にそろえているので、
「日付として 2/20 なら範囲内」と判定されます。
ここでの重要ポイントは、
「日付だけを判定したいときは、必ず .Date を通す」という習慣です。
これを忘れると、「同じ日なのに範囲外」と判定されるバグが生まれます。
DateOnly を使うとさらにシンプル(.NET 6以降)
.NET 6 以降なら、日付だけを扱う DateOnly が使えます。
日付範囲判定には非常に相性が良いです。
public static bool IsBetweenDateOnlyInclusive(DateOnly target, DateOnly from, DateOnly to)
{
return from <= target && target <= to;
}
C#使い方の例です。
DateOnly from = new DateOnly(2026, 2, 10);
DateOnly to = new DateOnly(2026, 2, 20);
DateOnly target = new DateOnly(2026, 2, 20);
bool inRange = IsBetweenDateOnlyInclusive(target, from, to); // true
C#DateOnly を使うと、「時刻をどうするか」を考えなくてよくなるので、
「日付だけの範囲」を扱うユーティリティには積極的に採用してよい型です。
実務で使える「日付範囲判定ユーティリティ」の形
期間オブジェクト+判定メソッドという設計
範囲判定をあちこちに生の比較で書くと、
「どこか一箇所だけ境界条件が違う」といった事故が起きやすくなります。
そこで、「期間」を表す小さなクラス(または struct)を作り、
その中に判定メソッドを持たせる設計がよく使われます。
public readonly struct DateRange
{
public DateTime From { get; }
public DateTime To { get; }
public DateRange(DateTime from, DateTime to)
{
if (to < from)
{
throw new ArgumentException("終了日は開始日以上である必要があります。");
}
From = from;
To = to;
}
public bool Contains(DateTime target)
{
return From <= target && target <= To;
}
public bool ContainsDateOnly(DateTime target)
{
DateTime d = target.Date;
DateTime df = From.Date;
DateTime dt = To.Date;
return df <= d && d <= dt;
}
}
C#使い方の例です。
var range = new DateRange(
new DateTime(2026, 2, 10, 0, 0, 0),
new DateTime(2026, 2, 20, 23, 59, 59));
DateTime t1 = new DateTime(2026, 2, 15, 12, 0, 0);
DateTime t2 = new DateTime(2026, 2, 21, 0, 0, 0);
Console.WriteLine(range.Contains(t1)); // true
Console.WriteLine(range.Contains(t2)); // false
Console.WriteLine(range.ContainsDateOnly(t2)); // false
C#ここでの重要ポイントは、
「範囲の定義(開始・終了・境界条件)を一箇所に閉じ込める」ことです。
これにより、ビジネスルールの変更(例:終了を含まないようにしたい)があっても、
このクラスだけ直せば全体の挙動を変えられます。
実務での落とし穴1:開始・終了の順序チェックを忘れる
「終了が開始より前」の不正な範囲
ユーザー入力や外部データから期間を受け取る場合、
「終了日が開始日より前」という不正な範囲が紛れ込むことがあります。
これをそのまま使うと、判定結果が直感と合わなくなります。
DateTime from = new DateTime(2026, 2, 20);
DateTime to = new DateTime(2026, 2, 10);
DateTime target = new DateTime(2026, 2, 15);
bool inRange = from <= target && target <= to; // 常に false
C#こうした不正な範囲は、
コンストラクタやファクトリメソッドで早めに弾いておくのが安全です。
先ほどの DateRange のように、to < from の場合は例外を投げる、
あるいは自動的に入れ替えるなど、
「どう扱うか」を最初に決めておきましょう。
実務での落とし穴2:日付と日時を混ぜて範囲判定する
「締切日を 0:00 で保存してしまう」問題
よくあるバグとして、
締切日を 2026/02/20 00:00:00 として保存してしまい、
当日の 10:00 に判定すると「もう範囲外」と扱われる、というものがあります。
DateTime from = new DateTime(2026, 2, 10, 0, 0, 0);
DateTime deadline = new DateTime(2026, 2, 20, 0, 0, 0);
DateTime now = new DateTime(2026, 2, 20, 10, 0, 0);
bool inRange = from <= now && now <= deadline; // false(当日なのに範囲外)
C#「日付としての期間」を扱いたいなら、
締切を「日付だけ」で判定するか、
「締切日の終わりの時刻」を明示的に決める必要があります。
日付だけで判定する例です。
bool inRange = from.Date <= now.Date && now.Date <= deadline.Date;
C#あるいは、「締切日の 23:59:59.999 まで有効」として保存する方法もありますが、
ミリ秒まで意識する必要が出てくるので、
多くの業務では「日付だけで比較する」ほうがシンプルです。
実務での落とし穴3:タイムゾーンを意識せずに範囲判定する
UTC とローカルが混ざると「日付がズレる」
サーバー内部で UTC を使い、画面では日本時間を表示しているようなシステムでは、
「どのタイムゾーンの“日付範囲”か」を意識しないと、
「日本時間では期間内なのに、UTC で見ると期間外」というズレが起こります。
例えば、日本時間で「2026/02/10〜2026/02/20」の期間を扱いたい場合、
UTC に変換すると開始・終了が前日や翌日にずれることがあります。
実務的には、
保存・比較は UTC で行う
ただし「日付範囲」はユーザーのタイムゾーンに変換してから判定する
という方針にしておくと安全です。
イメージとしては、次のような流れになります。
public static bool IsInUserDateRangeUtc(
DateTime utcTarget,
DateTime utcFrom,
DateTime utcTo,
TimeZoneInfo userTimeZone)
{
DateTime targetLocal = TimeZoneInfo.ConvertTimeFromUtc(utcTarget, userTimeZone);
DateTime fromLocal = TimeZoneInfo.ConvertTimeFromUtc(utcFrom, userTimeZone);
DateTime toLocal = TimeZoneInfo.ConvertTimeFromUtc(utcTo, userTimeZone);
return fromLocal.Date <= targetLocal.Date && targetLocal.Date <= toLocal.Date;
}
C#「どのタイムゾーンのカレンダーで範囲判定しているか」を
ユーティリティ名やコメントに刻んでおくと、後から読んだ人が迷いません。
まとめ 「日付範囲判定ユーティリティ」は“期間の意味を一箇所に固定するもの」
日付範囲判定は、一見シンプルですが、
開始・終了の含み方、日付か日時か、タイムゾーン、入力の妥当性など、
ビジネスルールの“線引き”がぎゅっと詰まったテーマです。
押さえておきたいポイントを整理すると、次のようになります。
範囲判定は「閉区間(from ≤ x ≤ to)」と「半開区間(from ≤ x < to)」のどちらかを選び、ルールを統一する。
日付だけの範囲を扱いたいときは .Date や DateOnly を使い、「時刻を無視する」ことをコードで明示する。
期間を表す小さな型(DateRange など)を用意し、その中に Contains のような判定メソッドを持たせると、ビジネスルールを一箇所に集約できる。
開始・終了の順序チェック(to < from)をコンストラクタなどで行い、不正な範囲を早めに弾く。
タイムゾーンや UTC/ローカルの違いを意識し、「どのカレンダーでの範囲か」を決めてから判定する。
ここを押さえておけば、
「なんとなく from <= x && x <= to と書いている」状態から一歩進んで、
“ビジネスの期間ルールを正しく表現できる、実務で使える日付範囲判定ユーティリティ”を
自分の C# コードの中に自信を持って組み込めるようになります。

