日付期間の重複チェック


# はじめに
業務アプリケーションでも、予約管理する機能を作成するときがあります。
その時に予約した日付期間が重複しているかチェックするわけですが、日付期間の重複チェックを複雑に記述される方を見られます。

しかし、これには定石のチェック方法があります。
「比較開始日付 <= 対象終了日付 AND 比較終了日付 >= 対象開始日付」
※条件に、＝が成り立っていいかどうかは仕様によります。

別ブログに書いた記事(2008年)ですが別ブログを破棄したので、情報を新たに変更してQiitaに移行しました。

【2019/10/06 追記】
重複するパターンとして対象期間より短いパターン⑦を追加しました。
これによるチェック方法に変更点はありませんが、テストパターンが無いことで誤解を生じさせてしまったみたいです。

また、定石のチェック方法の導き方について書かれた記事がありました。
[期間が重複しているかを判定する条件式の導出方法](http://yucatio.hatenablog.com/entry/2018/08/16/175914)


# 日付期間の重複チェックパターン

例では日付にしてますが、DateTime型を使ってますので時間でも同様です。

## 重複するパターン
![重複5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/facd9247-d85e-09e1-9e8f-a18796d8dc1a.png)

対象期間　2019/08/05 ～ 2019/09/15　
期間①　　2019/07/01 ～ 2019/08/31
期間②　　2019/08/10 ～ 2019/09/20
期間③　　2019/09/15 ～ 2019/10/10
期間④　　2019/08/01 ～ 2019/09/30
期間⑦　　2019/08/15 ～ 2019/09/05

```sql:条件式
比較開始日付 <= 対象終了日付 AND 比較終了日付 >= 対象開始日付
```

## 重複しないパターン
重複しない以下のパターンでは、定石の条件式は成立しません。
![重複無し2.png](https://qiita-image-store.s3.amazonaws.com/0/14053/eaac86f5-110a-27f9-4654-39c0daccc9c5.png)
対象期間　2019/08/05 ～ 2019/09/15
期間⑤　　2019/07/01 ～ 2019/08/04
期間⑥　　2019/09/16 ～ 2019/09/30

```sql:条件式
比較開始日付 > 対象終了日付 OR 比較終了日付 < 対象開始日付
```

# ソースコード
C#でLinqを使用しています。

```csharp
using System;
using System.Linq;
using System.Data;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable dtTable = new DataTable();
            DataRow drRow;

            dtTable.Columns.Add("START_DATE", Type.GetType("System.DateTime"));
            dtTable.Columns.Add("END_DATE", Type.GetType("System.DateTime"));
            drRow = dtTable.NewRow();

            //重複するパターン①～④
            drRow["START_DATE"] = DateTime.Parse("2019/07/01");
            drRow["END_DATE"] = DateTime.Parse("2019/08/31");
            dtTable.Rows.Add(drRow);
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/08/10");
            drRow["END_DATE"] = DateTime.Parse("2019/09/20");
            dtTable.Rows.Add(drRow);
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/09/15");
            drRow["END_DATE"] = DateTime.Parse("2019/10/10");
            dtTable.Rows.Add(drRow);
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/08/01");
            drRow["END_DATE"] = DateTime.Parse("2019/09/30");
            dtTable.Rows.Add(drRow);
            //重複するパターン⑦
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/08/15");
            drRow["END_DATE"] = DateTime.Parse("2019/09/05");
            dtTable.Rows.Add(drRow);

            //重複しないパターン⑤～⑥
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/07/01");
            drRow["END_DATE"] = DateTime.Parse("2019/08/04");
            dtTable.Rows.Add(drRow);
            drRow = dtTable.NewRow();
            drRow["START_DATE"] = DateTime.Parse("2019/09/16");
            drRow["END_DATE"] = DateTime.Parse("2019/09/30");
            dtTable.Rows.Add(drRow);

            //対象期間
            DateTime startdate = DateTime.Parse("2019/08/05");
            DateTime enddate = DateTime.Parse("2019/09/15");

            //重複している場合の抽出条件
            //比較開始日付 <= 対象終了日付 AND 比較終了日付 >= 対象開始日付
            var query = from x in dtTable.AsEnumerable()
                        where x.Field<DateTime>("START_DATE") <= enddate && x.Field<DateTime>("END_DATE") >= startdate
                        select x;

            int count = query.Count();  //7件中の5件抽出
            if (count > 0)
                Console.WriteLine(string.Format("{0}件の期間が重複しています。", count));


            //重複していない場合の抽出条件
            //比較開始日付 > 対象終了日付 OR 比較終了日付 < 対象開始日付
            var query2 = from x in dtTable.AsEnumerable()
                         where x.Field<DateTime>("START_DATE") > enddate || x.Field<DateTime>("END_DATE") < startdate
                         select x;

            int count2 = query2.Count();  //7件中の2件抽出
            if (count2 > 0)
                Console.WriteLine(string.Format("{0}件の期間が重複していません。", count2));
        }
    }
}
```
# 参考
* [2つの期間が重なり合うかどうかを判定する。](http://koseki.hatenablog.com/entry/20111021/range)
* [Life is beautiful: ビル・ゲイツの面接試験](http://satoshi.blogs.com/life/2005/06/post_2.html)
* [長方形の重なりを判定する問題](http://d.hatena.ne.jp/zariganitosh/20070606/1181092290)
* [SQLの日付期間の重複チェッククエリ](http://rikutoto.blogspot.jp/2013/09/sql.html)
* [SQLにおける期間重複チェックの考え方](http://dalmore.blog7.fc2.com/blog-entry-67.html)
* [期間が重複しているかを判定する条件式の導出方法](http://yucatio.hatenablog.com/entry/2018/08/16/175914)