Check if Two Date Ranges Overlap – 检查两个日期范围是否重叠

最后修改: 2024年 1月 24日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

In various applications such as appointments, bookings, or project timelines, avoiding scheduling conflicts is paramount. Overlapping dates can lead to inconsistencies and errors. In this tutorial, we’ll explore different scenarios of date range overlap and dive into various approaches and formulas to check for overlaps.

在约会、预订或项目时间表等各种应用中,避免日程冲突至关重要。日期重叠可能导致不一致和错误。在本教程中,我们将探讨日期范围重叠的不同情况,并深入研究检查重叠的各种方法和公式。

2. Understanding Overlapping Date Ranges

2.了解重叠的日期范围

Before we dive into the implementation, let’s ensure a clear understanding of what it means for two date ranges to overlap. Understanding the different scenarios that constitute date range overlap is crucial when implementing an efficient and accurate overlap checker. Let’s break down the various scenarios that need to be considered.

在深入实施之前,让我们确保清楚地了解两个日期范围重叠的含义。在实施高效、准确的重叠检查程序时,了解构成日期范围重叠的不同情况至关重要。让我们来分析一下需要考虑的各种情况。

2.1. Partial Overlap

2.1.部分重叠

Partial overlap occurs when one date range partially overlaps with another. This happens when two ranges share some portion of their timeframe but don’t entirely encompass each other. For example, if Project A spans from January 1st to February 10th and Project B runs from February 5th to March 1st, they partially overlap:

当一个日期范围与另一个日期范围部分重叠时,就会出现部分重叠。例如,如果项目 A 的时间跨度为 1 月 1 日至 2 月 10 日,而项目 B 的时间跨度为 2 月 5 日至 3 月 1 日,那么这两个项目就会部分重叠:

A                            B
|-----------------------------|
                C                                D
                |--------------------------------|

2.2. Full Overlap

2.2.完全重叠

Full overlap happens when one date range entirely encompasses another. This occurs when one date range completely falls within the boundaries of another. For example, if Booking A covers July 1st to July 31st and Booking B covers July 15th to July 25th, Booking B completely overlaps with Booking A:

完全重叠是指一个日期范围完全包含另一个日期范围。例如,如果预订 A 涵盖 7 月 1 日至 7 月 31 日,而预订 B 涵盖 7 月 15 日至 7 月 25 日,则预订 B 与预订 A 完全重叠:

A                                               B
|------------------------------------------------|
                C                      D
                |----------------------|

2.3. Consecutive Ranges

2.3.连续范围

Two ranges are considered adjacent if one ends immediately before the other begins. This is common in project timelines, where Project X spans from March 1st to March 31st, and Project Y runs from April 1st to April 30th. These ranges are adjacent but not overlapping:

如果一个范围在另一个范围开始之前结束,则认为这两个范围是相邻的。这在项目时间线中很常见,项目 X 从 3 月 1 日持续到 3 月 31 日,项目 Y 从 4 月 1 日持续到 4 月 30 日。这些范围相邻但不重叠:

A                    B
|---------------------|
                       C                    D
                       |--------------------|

2.4. Zero-Range Duration and Consecutive Ranges

2.4.零范围持续时间和连续范围

Zero-range duration refers to a scenario where the start and end dates of a range are the same, resulting in a duration of zero. For example, we may encounter an event scheduled for exactly one day with no duration. Although seemingly a special case, handling this scenario explicitly is essential to ensure the correctness of our date range overlap logic when considering consecutive ranges:

零范围持续时间指的是一个范围的开始和结束日期相同,导致持续时间为零的情况。例如,我们可能会遇到一个事件的计划时间正好是一天,但却没有持续时间。虽然这似乎是一种特殊情况,但在考虑连续范围时,明确处理这种情况对于确保日期范围重叠逻辑的正确性至关重要:

A                    B
|---------------------|
                      CD
                      |

3. Overlapping Formulas

3.重叠公式

Various mathematical formulas encapsulate the logic for determining date range overlap. Here, we’ll explore three commonly used formulas and explain their underlying principles.

各种数学公式封装了确定日期范围重叠的逻辑。下面,我们将探讨三个常用公式,并解释其基本原理。

Let’s first define the variables used in the formulas:

首先,让我们定义一下公式中使用的变量:

  • A: Start date of the first date range
  • B: End date of the first date range
  • C: Start date of the second date range
  • D: End date of the second date range

3.1. Calculating the Overlap Duration

3.1.计算重叠持续时间

This formula calculates the overlap duration by subtracting the earlier ending date from the later starting date:

该公式通过用较早的结束日期减去较晚的开始日期来计算重叠持续时间:

(min(B, D) - max(A, C)) >= 0

If this duration is negative, there’s no overlap. If it’s zero, the ranges might be touching, or one might be a single point in time. The application should consider whether to treat this scenario as an overlap or not. Moreover, a positive duration tells us there’s an overlap.

如果持续时间为负数,则没有重叠。如果为零,则两个范围可能相交,或其中一个范围可能是一个时间点。应用程序应考虑是否将这种情况视为重叠。此外,如果持续时间为正,则说明存在重叠。

3.2. Checking for Non-Overlap Conditions

3.2.检查非重叠条件

This formula checks for non-overlapping conditions between two date ranges. If either condition fails, then there’s an overlap:

该公式检查两个日期范围之间是否存在非重叠条件。如果任一条件失败,则说明存在重叠:

!(B <= C || A >= D)

Take note that if we change the condition to use strict inequality “<” and “>”, it means the ranges must not touch at all. There should be no common point between the two ranges. Once again, the application should decide whether to treat this scenario as an overlap or not.

请注意,如果我们将条件改为使用严格不等式”<“和”>”,这意味着两个范围必须完全不接触。两个范围之间不应有共同点。同样,应用程序应决定是否将这种情况视为重叠。

3.3. Finding Minimum Overlap

3.3.寻找最小值重叠

This implementation calculates the overlap duration in days by considering the minimum of the four calculations. If the minimum overlap duration is zero or negative, it implies that there is no overlap, as the ranges do not intersect in a non-zero duration. A positive minimum overlap duration indicates an actual overlap, suggesting that the ranges share a duration greater than zero:

该实现方法通过考虑四种计算方法中的最小值来计算以天为单位的重叠持续时间。如果最小重叠持续时间为零或负数,则表示没有重叠,因为两个范围在非零持续时间内没有交集。如果最小重叠持续时间为正数,则表明存在实际重叠,即范围共享的持续时间大于零:

min((B - A), (B - C), (D - A), (D - C)) >= 0

However, if we change it to just greater than zero, the condition becomes stricter. This means there must be more than just a touch at a single point and must be a non-zero duration of overlap.

但是,如果我们将其改为大于零,条件就会变得更加严格。这就意味着,必须不仅仅是在单点上的触碰,还必须有非零的重叠时间。

4. Implementation

4.实施

Now that we’ve explored the different overlapping scenarios and formulas, let’s dive into implementing code to accurately detect these overlaps using various approaches.

既然我们已经探究了不同的重叠情况和公式,那就让我们深入研究如何使用各种方法执行代码来准确检测这些重叠。

4.1. Using Calendar

4.1.使用日历</em

We’ll leverage the Calendar class to manage date ranges and check for overlaps. The Calendar class is part of the java.util package and provides a way to work with dates and times. The getTimeInMillis() method is used to obtain the time in milliseconds for each Calendar instance.

我们将利用Calendar类来管理日期范围并检查重叠。Calendar类是java.util包的一部分,它提供了一种处理日期和时间的方法。getTimeInMillis() 方法用于获取每个 Calendar 实例的毫秒时间。

In this example, we’ll leverage the Math.min() and Math.max() methods to calculate the overlap duration by subtracting the earlier ending date from the later starting date:

在本例中,我们将利用 Math.min()Math.max() 方法,通过从较后的开始日期减去较早的结束日期来计算重叠持续时间:

boolean isOverlapUsingCalendarAndDuration(Calendar start1, Calendar end1, Calendar start2, Calendar end2) {
    long overlap = Math.min(end1.getTimeInMillis(), end2.getTimeInMillis()) -
      Math.max(start1.getTimeInMillis(), start2.getTimeInMillis());
    return overlap > 0;
}

To implement the non-overlap conditions checking, we’ll utilize the before() and after() methods provided by the Calendar class. These methods assess the chronological relationship between date instances and are essential for determining overlap. If either condition is true, it indicates an overlap:

为了实现非重叠条件检查,我们将使用 Calendar 类提供的 before()after() 方法。这些方法用于评估日期实例之间的时间关系,对于确定重叠至关重要。如果任一条件为 true,则表明存在重叠:

boolean isOverlapUsingCalendarAndCondition(Calendar start1, Calendar end1, Calendar start2, Calendar end2) {
    return !(end1.before(start2) || start1.after(end2));
}

Now, let’s implement the logic using the find minimum formula. This method calculates the minimum overlap duration among different scenarios:

现在,让我们使用查找最小值公式来实现逻辑。这种方法可以计算出不同方案之间的最短重叠时间:

boolean isOverlapUsingCalendarAndFindMin(Calendar start1, Calendar end1, Calendar start2, Calendar end2) {
    long overlap1 = Math.min(end1.getTimeInMillis() - start1.getTimeInMillis(), 
      end1.getTimeInMillis() - start2.getTimeInMillis());
    long overlap2 = Math.min(end2.getTimeInMillis() - start2.getTimeInMillis(), 
      end2.getTimeInMillis() - start1.getTimeInMillis());

   return Math.min(overlap1, overlap2) / (24 * 60 * 60 * 1000) >= 0;
}

To ensure the correctness of our implementation, we can set up test data using Calendar instances representing different date ranges.

为了确保实现的正确性,我们可以使用代表不同日期范围的 Calendar 实例来设置测试数据。

First, let’s create the start date range:

首先,创建起始日期范围:

Calendar start1 = Calendar.getInstance().set(2024, 11, 15); 
Calendar end1 = Calendar.getInstance().set(2024, 11, 20);

Subsequently, we can set the end date range to partially overlap:

随后,我们可以将结束日期范围设置为部分重叠:

Calendar start2 = Calendar.getInstance()set(2024, 11, 18);
Calendar end2 = Calendar.getInstance().set(2024, 11, 22);

assertTrue(isOverlapUsingCalendarAndDuration(start1, end1, start2, end2));
assertTrue(isOverlapUsingCalendarAndCondition(start1, end1, start2, end2));
assertTrue(isOverlapUsingCalendarAndFindMin(start1, end1, start2, end2));

Here’s the corresponding unit test code to see if the test data is fully overlapping:

下面是相应的单元测试代码,用于查看测试数据是否完全重叠:

Calendar start2 = Calendar.getInstance()set(2024, 11, 16);
Calendar end2 = Calendar.getInstance().set(2024, 11, 18);

assertTrue(isOverlapUsingCalendarAndDuration(start1, end1, start2, end2));
assertTrue(isOverlapUsingCalendarAndCondition(start1, end1, start2, end2));
assertTrue(isOverlapUsingCalendarAndFindMin(start1, end1, start2, end2));

Finally, we set the end date range to consecutive ranges, and we should expect no overlapping:

最后,我们将结束日期范围设置为连续范围,预计不会出现重叠:

Calendar start2 = Calendar.getInstance()set(2024, 11, 21);
Calendar end2 = Calendar.getInstance().set(2024, 11, 24);

assertFalse(isOverlapUsingCalendarAndDuration(start1, end1, start2, end2)); 
assertFalse(isOverlapUsingCalendarAndCondition(start1, end1, start2, end2)); 
assertFalse(isOverlapUsingCalendarAndFindMin(start1, end1, start2, end2));

4.2. Using Java 8’s LocalDate

4.2.使用 Java 8 的 LocalDate

Java 8 introduced the java.time.LocalDate class, which provides a more modern and convenient way to handle dates. We can leverage this class to simplify our date range overlap check. In the calculation of the overlap duration, we utilize the toEpochDay() method provided by the LocalDate class. This method is used to obtain the number of days from the epoch day for a given LocalDate:

Java 8 引入了java.time.LocalDate类,该类提供了一种更现代、更方便的方式来处理日期。在计算重叠持续时间时,我们使用了 LocalDate 类提供的 toEpochDay() 方法。该方法用于获取给定 LocalDate 的从纪元日起的天数:

boolean isOverlapUsingLocalDateAndDuration(LocalDate start1, LocalDate end1, LocalDate start2, LocalDate end2) {
    long overlap = Math.min(end1.toEpochDay(), end2.toEpochDay()) -
      Math.max(start1.toEpochDay(), start2.toEpochDay());

    return overlap >= 0;
}

The LocalDate class provides two essential methods, isBefore() and isAfter(), for assessing the chronological relationship between LocalDate instances. We’ll utilize both methods to determine the non-overlap conditions:

LocalDate 类提供了两个基本方法:isBefore()isAfter(),用于评估 LocalDate 实例之间的时间关系。我们将利用这两个方法来确定非重叠条件:

boolean isOverlapUsingLocalDateAndCondition(LocalDate start1, LocalDate end1, LocalDate start2, LocalDate end2) {
    return !(end1.isBefore(start2) || start1.isAfter(end2));
}

Next, we’ll explore the use of LocalDate to find the minimum overlap between two date ranges:

接下来,我们将探讨如何使用 LocalDate 来查找两个日期范围之间的最小重叠:

boolean isOverlapUsingLocalDateAndFindMin(LocalDate start1, LocalDate end1, LocalDate start2, LocalDate end2) {
    long overlap1 = Math.min(end1.toEpochDay() - start1.toEpochDay(), 
      end1.toEpochDay() - start2.toEpochDay());
    long overlap2 = Math.min(end2.toEpochDay() - start2.toEpochDay(), 
      end2.toEpochDay() - start1.toEpochDay());

    return Math.min(overlap1, overlap2) >= 0;
}

Now, let’s set up test data using LocaleDate instances representing different date ranges.

现在,让我们使用代表不同日期范围的 LocaleDate 实例来设置测试数据。

First, let’s create the start date range:

首先,创建起始日期范围:

LocalDate start1 = LocalDate.of(2024, 11, 15); 
LocalDate end1 = LocalDate.of(2024, 11, 20);

Next, let’s set the end date range to partially overlap:

接下来,让我们把结束日期范围设置为部分重叠:

LocalDate start1 = LocalDate.of(2024, 11, 15); 
LocalDate end1 = LocalDate.of(2024, 11, 20);

assertTrue(isOverlapUsingLocalDateAndDuration(start1, end1, start2, end2));
assertTrue(isOverlapUsingLocalDateAndCondition(start1, end1, start2, end2));
assertTrue(isOverlapUsingLocalDateAndFindMin(start1, end1, start2, end2));

We’ll follow this by setting the end date range to fully overlap:

接下来,我们将把结束日期范围设置为完全重叠:

LocalDate start1 = LocalDate.of(2024, 11, 16); 
LocalDate end1 = LocalDate.of(2024, 11, 18);

assertTrue(isOverlapUsingLocalDateAndDuration(start1, end1, start2, end2));
assertTrue(isOverlapUsingLocalDateAndCondition(start1, end1, start2, end2));
assertTrue(isOverlapUsingLocalDateAndFindMin(start1, end1, start2, end2));

Finally, when we set the end date range to consecutive ranges, we should expect no overlapping:

最后,当我们将结束日期范围设置为连续的范围时,我们应该预计不会出现重叠:

LocalDate start1 = LocalDate.of(2024, 11, 21); 
LocalDate end1 = LocalDate.of(2024, 11, 24);

assertFalse(isOverlapUsingLocalDateAndDuration(start1, end1, start2, end2)); 
assertFalse(isOverlapUsingLocalDateAndCondition(start1, end1, start2, end2)); 
assertFalse(isOverlapUsingLocalDateAndFindMin(start1, end1, start2, end2));

4.3. Using Joda-Time

4.3.使用 Joda-Time

Joda-Time, a widely adopted library for date and time operations in Java, simplifies complex temporal calculations and offers a convenient method called overlaps() to determine if two intervals overlap.

Joda-Time 是 Java 中广泛采用的日期和时间操作库,它简化了复杂的时间计算,并提供了一个名为 overlaps() 的便捷方法来确定两个时间间隔是否重叠。

The overlaps() method takes two Interval objects as parameters and returns true if there is any intersection between the two intervals. In other words, it identifies whether there is any shared duration between the specified date ranges.

overlaps()方法将两个 Interval 对象作为参数,如果两个时间间隔之间存在任何交集,则返回 true 。换句话说,它会识别指定日期范围之间是否存在任何共享的持续时间。

However, it is important to note that Joda-Time considers two intervals with the same start and end points as non-overlapping. This behavior is especially relevant when dealing with scenarios where precise boundaries matter and even a point in time is not considered an overlap.

不过,需要注意的是,Joda-Time 将具有相同起点和终点的两个区间视为非重叠区间。在处理精确边界非常重要的场景时,这种行为尤其重要,因为即使是一个时间点也不会被视为重叠。

Let’s see an implementation using Joda-Time:

让我们看看使用 Joda-Time 的实现:

boolean isOverlapUsingJodaTime(DateTime start1, DateTime end1, DateTime start2, DateTime end2) {
    Interval interval1 = new Interval(start1, end1);
    Interval interval2 = new Interval(start2, end2);

    return interval1.overlaps(interval2);
}

Now, let’s set up test data using Interval instances representing different date ranges. We begin with the partial overlapping:

现在,让我们使用代表不同日期范围的 Interval 实例来设置测试数据。我们从部分重叠开始:

DateTime startJT1 = new DateTime(2024, 12, 15, 0, 0);
DateTime endJT1 = new DateTime(2024, 12, 20, 0, 0);
DateTime startJT2 = new DateTime(2024, 12, 18, 0, 0);
DateTime endJT2 = new DateTime(2024, 12, 22, 0, 0);

assertTrue(isOverlapUsingJodaTime(startJT1, endJT1, startJT2, endJT2));

Let’s change the end date range to fully overlap:

让我们把结束日期范围改为完全重叠:

DateTime startJT2 = new DateTime(2024, 12, 16, 0, 0);
DateTime endJT2 = new DateTime(2024, 12, 18, 0, 0);

assertTrue(isOverlapUsingJodaTime(startJT1, endJT1, startJT2, endJT2));

Finally, we change the end date range to a consecutive range, and again, it should return no overlapping:

最后,我们将结束日期范围改为连续范围,同样,返回的结果应该没有重叠:

DateTime startJT2 = new DateTime(2024, 12, 21, 0, 0);
DateTime endJT2 = new DateTime(2024, 12, 24, 0, 0);

assertFalse(isOverlapUsingJodaTime(startJT1, endJT1, startJT2, endJT2));

5. Conclusion

5.结论

In this article, we explored various scenarios, mathematical formulas, and approaches to check if two date ranges overlap in Java. Understanding partial overlap, full overlap, consecutive ranges, and zero-range duration gave us a solid foundation for our exploration.

在本文中,我们探索了在 Java 中检查两个日期范围是否重叠的各种情况、数学公式和方法。对部分重叠、完全重叠、连续范围和零范围持续时间的了解为我们的探索奠定了坚实的基础。

As always, the code is available over on GitHub.

与往常一样,代码可在 GitHub 上获取。