Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cn.hutool.core.date.LocalDateTimeUtil#isOverlap 重叠定义有误 #3215

Closed
izerok opened this issue Jul 19, 2023 · 3 comments
Closed

cn.hutool.core.date.LocalDateTimeUtil#isOverlap 重叠定义有误 #3215

izerok opened this issue Jul 19, 2023 · 3 comments

Comments

@izerok
Copy link

izerok commented Jul 19, 2023

版本情况

JDK版本: openjdk_8_201
hutool版本: 5.X.X(请确保最新尝试是否还有问题)

问题描述(包括截图)

检查两个时间段是否有时间重叠

cn.hutool.core.date.LocalDateTimeUtil#isOverlap 该工具类注释声明的是判断时间重叠

https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html
对于艾伦代数区间的定义,重叠仅指的是一个活动的时间段完全或部分包含在另一个活动的时间段内才存在重叠,而如果两个活动仅在开始或结束时间点上相连,则不视为存在重叠,即使在时间轴上它们是连续的。

对于该代码中的实现

public static boolean isOverlap(ChronoLocalDateTime<?> realStartTime, ChronoLocalDateTime<?> realEndTime,
									ChronoLocalDateTime<?> startTime, ChronoLocalDateTime<?> endTime) {
		return realStartTime.compareTo(endTime) <=0 && startTime.compareTo(realEndTime) <= 0;
	}

理论上应该只表达 重叠(overlap)的逻辑 而不是 重叠(overlap)+遇见(meet)
代码示例:

public static boolean overlaps(LocalDateTime start1, LocalDateTime end1, LocalDateTime start2, LocalDateTime end2) {
        return start1.isBefore(end2) && start2.isBefore(end1) && !start1.isEqual(start2) && !end1.isEqual(end2);
    }

验证代码

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.log.StaticLog;

import java.time.LocalDateTime;

public class OverLapsTest {
    public static void main(String[] args) {
        String lineOneStart = "2021-01-01 10:00:00";
        String lineOneEnd = "2021-01-01 11:00:00";
        String lineTwoStart = "2021-01-01 11:00:00";
        String lineTwoEnd = "2021-01-01 12:00:00";
        String lineThreeStart = "2021-01-01 12:00:00";
        String lineThreeEnd = "2021-01-01 13:00:00";

        String lineFourStart = "2021-01-01 10:30:00";
        String lineFourEnd = "2021-01-01 11:30:00";

        LocalDateTime lineOneStartDateTime = LocalDateTimeUtil.parse(lineOneStart, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineOneEndDateTime = LocalDateTimeUtil.parse(lineOneEnd, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineTwoStartDateTime = LocalDateTimeUtil.parse(lineTwoStart, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineTwoEndDateTime = LocalDateTimeUtil.parse(lineTwoEnd, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineThreeStartDateTime = LocalDateTimeUtil.parse(lineThreeStart, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineThreeEndDateTime = LocalDateTimeUtil.parse(lineThreeEnd, DatePattern.NORM_DATETIME_FORMATTER);

        LocalDateTime lineFourStartDateTime = LocalDateTimeUtil.parse(lineFourStart, DatePattern.NORM_DATETIME_FORMATTER);
        LocalDateTime lineFourEndDateTime = LocalDateTimeUtil.parse(lineFourEnd, DatePattern.NORM_DATETIME_FORMATTER);

        boolean overlap1 = OverLapsTest.overlaps(lineOneStartDateTime, lineOneEndDateTime, lineTwoStartDateTime, lineTwoEndDateTime);
        StaticLog.info("overlap: {}", overlap1);
        boolean overlap2 = OverLapsTest.overlaps(lineTwoStartDateTime, lineTwoEndDateTime, lineThreeStartDateTime, lineThreeEndDateTime);
        StaticLog.info("overlap: {}", overlap2);
        boolean overlap3 = OverLapsTest.overlaps(lineOneStartDateTime, lineOneEndDateTime, lineThreeStartDateTime, lineThreeEndDateTime);
        StaticLog.info("overlap: {}", overlap3);

        boolean overlap4 = OverLapsTest.overlaps(lineOneStartDateTime, lineOneEndDateTime, lineFourStartDateTime, lineFourEndDateTime);
        StaticLog.info("overlap: {}", overlap4);
        boolean overlap5 = OverLapsTest.overlaps(lineTwoStartDateTime, lineTwoEndDateTime, lineFourStartDateTime, lineFourEndDateTime);
        StaticLog.info("overlap: {}", overlap5);
        boolean overlap6 = OverLapsTest.overlaps(lineOneStartDateTime, lineOneEndDateTime, lineOneStartDateTime, lineOneEndDateTime);
        StaticLog.info("overlap: {}", overlap6);
    }

    public static boolean overlaps(LocalDateTime start1, LocalDateTime end1, LocalDateTime start2, LocalDateTime end2) {
        return start1.isBefore(end2) && start2.isBefore(end1) && !start1.isEqual(start2) && !end1.isEqual(end2);
    }
}

输出结果

OverLapsTest: overlap: false
OverLapsTest: overlap: false
OverLapsTest: overlap: false
OverLapsTest: overlap: true
OverLapsTest: overlap: true
OverLapsTest: overlap: true
  1. 复现代码
    cn.hutool.core.date.LocalDateTimeUtil#isOverlap
  1. 堆栈信息

  2. 测试涉及到的文件(注意脱密)

比如报错的Excel文件,有问题的图片等。

@CherryRum
Copy link
Collaborator

所以这个是不完全实现,只能在一定程度上对这个做解释,具体原因见,#2725
这个不是一个纯代数上面的考虑,更加偏向现实一点,
有空我会重新实现一下
以下纯代数上的定义
image

@izerok izerok closed this as completed Jul 20, 2023
@CherryRum
Copy link
Collaborator

v6会添加以下方法

class AllenRelation{
    enum AllenIntervalRelation {
        STARTS((i1, i2) -> i1.start.equals(i2.start) && i1.end.isBefore(i2.end)),
        STARTED_BY((i1, i2) -> i1.start.equals(i2.start) && i1.end.isAfter(i2.end)),
        DURING((i1, i2) -> i1.start.isAfter(i2.start) && i1.end.isBefore(i2.end)),
        CONTAINS((i1, i2) -> i1.start.isBefore(i2.start) && i1.end.isAfter(i2.end)),
        FINISHES((i1, i2) -> i1.end.equals(i2.end) && i1.start.isAfter(i2.start)),
        FINISHED_BY((i1, i2) -> i1.end.equals(i2.end) && i1.start.isBefore(i2.start)),
        PRECEDES((i1, i2) -> i1.end.isBefore(i2.start)),
        PRECEDED_BY((i1, i2) -> i1.start.isAfter(i2.end)),
        MEETS((i1, i2) -> i1.end.equals(i2.start)),
        MET_BY((i1, i2) -> i1.start.equals(i2.end)),
        OVERLAPS((i1, i2) -> i1.start.isBefore(i2.start) && i1.end.isAfter(i2.start) && i1.end.isBefore(i2.end)),
        OVERLAPPED_BY((i1, i2) -> i1.start.isAfter(i2.start) && i1.start.isBefore(i2.end) && i1.end.isAfter(i2.end)),
        EQUALS((i1, i2) -> i1.start.equals(i2.start) && i1.end.equals(i2.end));

        private final BiPredicate<Interval, Interval> predicate;

        AllenIntervalRelation(BiPredicate<Interval, Interval> predicate) {
            this.predicate = predicate;
        }

        public static AllenIntervalRelation getRelation(Interval i1, Interval i2) {
            for (AllenIntervalRelation relation : values()) {
                if (relation.predicate.test(i1, i2)) {
                    return relation;
                }
            }
           return null;
        }

        public static class Interval {
            LocalDateTime start;
            LocalDateTime end;
            Interval(LocalDateTime start, LocalDateTime end) {
                this.start = start;
                this.end = end;
            }
        }
    }

    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        // For the EQUALS relation
        AllenIntervalRelation.Interval i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        AllenIntervalRelation.Interval i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected EQUALS: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the STARTS relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusHours(2));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected STARTS: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the STARTED_BY relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now, now.plusHours(2));
        System.out.println("Expected STARTED_BY: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the DURING relation
        i1 = new AllenIntervalRelation.Interval(now.plusHours(1), now.plusHours(2));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected DURING: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the CONTAINS relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now.plusHours(1), now.plusHours(2));
        System.out.println("Expected CONTAINS: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the FINISHES relation
        i1 = new AllenIntervalRelation.Interval(now.plusHours(2), now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected FINISHES: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the FINISHED_BY relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now.plusHours(2), now.plusDays(1));
        System.out.println("Expected FINISHED_BY: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the PRECEDES relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now.plusDays(2), now.plusDays(3));
        System.out.println("Expected PRECEDES: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the PRECEDED_BY relation
        i1 = new AllenIntervalRelation.Interval(now.plusDays(2), now.plusDays(3));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected PRECEDED_BY: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the MEETS relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        i2 = new AllenIntervalRelation.Interval(now.plusDays(1), now.plusDays(2));
        System.out.println("Expected MEETS: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the MET_BY relation
        i1 = new AllenIntervalRelation.Interval(now.plusDays(1), now.plusDays(2));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(1));
        System.out.println("Expected MET_BY: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the OVERLAPS relation
        i1 = new AllenIntervalRelation.Interval(now, now.plusDays(2));
        i2 = new AllenIntervalRelation.Interval(now.plusDays(1), now.plusDays(3));
        System.out.println("Expected OVERLAPS: " + AllenIntervalRelation.getRelation(i1, i2));

        // For the OVERLAPPED_BY relation
        i1 = new AllenIntervalRelation.Interval(now.plusDays(1), now.plusDays(3));
        i2 = new AllenIntervalRelation.Interval(now, now.plusDays(2));
        System.out.println("Expected OVERLAPPED_BY: " + AllenIntervalRelation.getRelation(i1, i2));
    }
}

@looly
Copy link
Member

looly commented Jul 29, 2023

@CherryRum 嗯……搞这么复杂么,期待PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants