Help us understand the problem. What is going on with this article?

【Java】日付の期間重複チェックサンプル

概要

日付で期間を持ってるデータを登録するときにすでに登録してある期間と重複しないようにしたい。
LocalDateを使ったJavaのサンプルがあまり見当たらなかったので公開します。

環境

  • Java 1.8
  • SpringBoot 2.2.1.RELEASE
  • thymeleaf 3.0.11.RELEASE

期間が重複するパターン

日付の期間が重複するパターンは全部で4つ。
Qiita用画像.png
1. 前半がかぶるパターン(①の終了日と緑の開始日が同じも含む)
2. 後半がかぶるパターン(②の開始日と緑の終了日が同じも含む)
3. 全部かぶるパターン(開始日と終了日が全く同じも含む)
4. 一部がかぶるパターン

全パターンを網羅できる条件式は下記の通り。

条件式
.開始日 <= .終了日 && .終了日 => .開始日

サンプル

画面から開始日と終了日を入力して、その入力期間が既存の期間(複数)と重複していない場合だけ登録できるサンプル。

登録する日付期間を持つModelクラス

登録する開始日と終了日、自動採番のIDを持つ画面に渡すModelクラス。

DurationModel.java
package com.tamorieeeen.sample.model;

import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 *
 * @author tamorieeeen
 *
 */
@Getter
@Setter
@NoArgsConstructor
public class DurationModel {

    // 保存時にauto_incrementで採番
    private int id;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}

期間重複判定ロジックServiceクラス

Controllerから呼び出されるServiceクラス。

DurationService.java
package com.tamorieeeen.sample.service;

import java.time.LocalDate;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
import com.tamorieeeen.sample.model.DurationModel;

/**
 *
 * @author tamorieeeen
 *
 */
@Service
public class DurationService {

    /**
     * すでに登録済の期間とかぶってないかチェック
     */
    public boolean isInvalid(DurationModel model) {

        return this.getDurationList()
                .stream()
                .filter(u -> model.getId() != u.getId()) // ※1
                .anyMatch(u ->
                        (order.getStartDate().isBefore(u.getEndDate())
                        && order.getEndDate().isAfter(u.getStartDate()))
                        || order.getStartDate().isEqual(u.getEndDate())
                        || order.getEndDate().isEqual(u.getStartDate()));
    }

    /**
     * 一覧を取得
     */
    private List<DurationModel> getDurationList() {

        // TODO すでに登録済のデータを取得
    }

    /**
     * 新規登録/更新
     */
    @Transactional
    public void saveDuration(DurationModel model) {

        // TODO DBなどへのデータ保存処理
    }
}

※1: 比較元(model)のIDは期間チェックを除外する
こうしないとデータ更新時にthis.getDurationList()に比較元データも含まれているため重複扱いでvalidationに引っかかってしまうため。
新規登録しか考えないのであれば、この行は不要。

Controllerクラス

実際はバリデーションチェックに@ValidatedBindingResultを使ってるけどその部分は省略。

DurationController.java
/**
 *
 * @author tamorieeeen
 *
 */
@Controller
public class DurationController {

    @Autowired
    private DurationService durationService;

    /**
     * 新規登録
     */
    @GetMapping("/duration/register")
    public String register(Model model) {

        model.addAttribute("duration", new DurationModel());

        return "duration/register";
    }

    /**
     * 新規登録処理
     */
    @PostMapping("/duration/register")
    public String registerComplete(Model model,
            @ModelAttribute("duration") DurationModel duration,
            RedirectAttributes redirect) {

        // バリデーションチェック
        if (durationService.isInvalid(duration)) {

            model.addAttribute("invalid", true);

            return "duration/register";
        }

        durationService.saveDuration(duration);

        redirect.addFlashAttribute("complete", true);

        return "redirect:/duration/register";
    }
}

画面側html(thymeleaf)

htmlヘッダーは共通化しているが今回は関係ないので省略。

register.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common :: meta_header('sample',~{::link},~{::script},~{::meta})">
</head>
<body>
    <div th:if="${complete}">
        <p>期間を登録しました。</p>
    </div>
    <div th:if="${invalid}">
        <p>期間が重複しているため、登録できません。</p>
    </div>
    <form th:action="@{/duration/register}" method="post" th:object="${duration}">
        <table>
            <tr><td>開始日</td><td>
                <input type="date" th:field="*{startDate}" th:value="*{startDate}" />
            </td></tr>
            <tr><td>終了日</td><td>
                <input type="date" th:field="*{endDate}" th:value="*{endDate}" />
            </td></tr>
        </table>
        <input type="button" th:value="登録する" onclick="submit();" />
    </form>
</body>
</html>

参考

tamorieeeen
Web系フルスタックフリーランスエンジニア。Java/PHP/MySQL/HTML/CSS/JavaScript/AWS/RubyとGoは勉強中
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした