檢查兩個日期範圍是否重疊
一、簡介
在約會、預訂或專案時間表等各種應用程式中,避免日程安排衝突至關重要。重疊的日期可能會導致不一致和錯誤。在本教程中,我們將探索日期範圍重疊的不同場景,並深入研究檢查重疊的各種方法和公式。
2. 了解重疊的日期範圍
在我們深入實施之前,讓我們確保清楚地了解兩個日期範圍重疊的含義。在實施高效且準確的重疊檢查器時,了解構成日期範圍重疊的不同場景至關重要。讓我們分解一下需要考慮的各種場景。
2.1.部分重疊
當一個日期範圍與另一個日期範圍部分重疊時,就會發生部分重疊。當兩個範圍共享其時間範圍的某些部分但不完全包含彼此時,就會發生這種情況。例如,如果專案 A 的運行時間為 1 月 1 日至 2 月 10 日,專案 B 的運行時間為 2 月 5 日至 3 月 1 日,則它們部分重疊:
AB
|-----------------------------|
CD
|--------------------------------|
2.2 完全重疊
當一個日期範圍完全包含另一個日期範圍時,就會發生完全重疊。當一個日期範圍完全落入另一個日期範圍的邊界時,就會發生這種情況。例如,如果預訂 A 涵蓋 7 月 1 日至 7 月 31 日,預訂 B 涵蓋 7 月 15 日至 7 月 25 日,則預訂 B 與預訂 A 完全重疊:
AB
|------------------------------------------------|
CD
|----------------------|
2.3 連續範圍
如果兩個範圍在另一個範圍開始之前立即結束,則認為兩個範圍相鄰。這在專案時間表中很常見,其中專案 X 從 3 月 1 日到 3 月 31 日,專案 Y 從 4 月 1 日到 4 月 30 日。這些範圍相鄰但不重疊:
AB
|---------------------|
CD
|--------------------|
2.4.零範圍持續時間和連續範圍
零範圍持續時間是指範圍的開始日期和結束日期相同、導致持續時間為零的情況。例如,我們可能會遇到一個安排在一天但沒有持續時間的事件。儘管看似特殊情況,但在考慮連續範圍時,明確處理這種情況對於確保日期範圍重疊邏輯的正確性至關重要:
AB
|---------------------|
CD
|
3. 重疊公式
各種數學公式封裝了確定日期範圍重疊的邏輯。在這裡,我們將探討三個常用的公式並解釋它們的基本原理。
我們先定義公式中使用的變數:
- A:第一個日期範圍的開始日期
- B:第一個日期範圍的結束日期
- C:第二個日期範圍的開始日期
- D:第二個日期範圍的結束日期
3.1.計算重疊持續時間
此公式透過從較晚的開始日期中減去較早的結束日期來計算重疊持續時間:
(min(B, D) - max(A, C)) >= 0
如果此持續時間為負,則不存在重疊。如果為零,則範圍可能會接觸,或者可能是單一時間點。應用程式應考慮是否將此場景視為重疊。此外,正的持續時間告訴我們有重疊。
3.2.檢查非重疊條件
此公式檢查兩個日期範圍之間的不重疊條件。如果任一條件失敗,則存在重疊:
!(B <= C || A >= D)
請注意,如果我們更改條件以使用嚴格不等式“<”和“>”,則意味著範圍不能完全接觸。兩個範圍之間不應有共同點。再次,應用程式應決定是否將此場景視為重疊。
3.3.尋找最小**重疊**
此實現透過考慮四個計算中的最小值來計算重疊持續時間(以天為單位)。如果最小重疊持續時間為零或負數,則表示不存在重疊,因為範圍在非零持續時間中不相交。正的最小重疊持續時間表示實際重疊,表示範圍共享大於零的持續時間:
min((B - A), (B - C), (D - A), (D - C)) >= 0
但是,如果我們將其更改為僅大於零,則條件會變得更嚴格。這意味著必須不僅僅是在單點上進行觸摸,而且必須是非零重疊持續時間。
4. 實施
現在我們已經探索了不同的重疊場景和公式,接下來讓我們深入研究實作程式碼,以使用各種方法準確檢測這些重疊。
4.1.使用Calendar
我們將利用Calendar
類別來管理日期範圍並檢查重疊。 Calendar類別是java.util
套件的一部分,提供了一種處理日期和時間的方法。 getTimeInMillis()
方法用於取得每個Calendar
實例的時間(以毫秒為單位)。
在此範例中,我們將利用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;
}
為了實現非重疊條件檢查,我們將利用Calendar
類別提供的before()
和after()
方法。這些方法評估日期實例之間的時間順序關係,對於確定重疊至關重要。如果任一條件為true
,則表示重疊:
boolean isOverlapUsingCalendarAndCondition(Calendar start1, Calendar end1, Calendar start2, Calendar end2) {
return !(end1.before(start2) || start1.after(end2));
}
現在,讓我們使用查找最小值公式來實現邏輯。此方法計算不同場景之間的最小重疊持續時間:
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;
}
為了確保我們實現的正確性,我們可以使用代表不同日期範圍的Calendar
實例來設定測試資料。
首先,讓我們建立開始日期範圍:
Calendar start1 = Calendar.getInstance().set(2024, 11, 15);
Calendar end1 = Calendar.getInstance().set(2024, 11, 20);
隨後,我們可以將結束日期範圍設定為部分重疊:
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));
以下是對應的單元測試程式碼,用於查看測試資料是否完全重疊:
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));
最後,我們將結束日期範圍設定為連續範圍,我們應該期望不會重疊:
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.使用 Java 8 的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;
}
LocalDate
類別提供了兩個基本方法isBefore()
和isAfter(),
用於評估LocalDate
實例之間的時間順序關係。我們將利用這兩種方法來確定非重疊條件:
boolean isOverlapUsingLocalDateAndCondition(LocalDate start1, LocalDate end1, LocalDate start2, LocalDate end2) {
return !(end1.isBefore(start2) || start1.isAfter(end2));
}
接下來,我們將探索如何使用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;
}
現在,讓我們使用代表不同日期範圍的LocaleDate
實例來設定測試資料。
首先,讓我們建立開始日期範圍:
LocalDate start1 = LocalDate.of(2024, 11, 15);
LocalDate end1 = LocalDate.of(2024, 11, 20);
接下來,讓我們將結束日期範圍設定為部分重疊:
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));
我們將透過將結束日期範圍設為完全重疊來遵循此要求:
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));
最後,當我們將結束日期範圍設定為連續範圍時,我們應該期望不會重疊:
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 使用Joda-Time
Joda-Time
是 Java 中廣泛採用的日期和時間操作庫,它簡化了複雜的時間計算,並提供了一種稱為overlaps()
的便捷方法來確定兩個間隔是否重疊。
overlaps()
方法接受兩個Interval
物件作為參數,如果兩個間隔之間存在交集,則傳回true
。換句話說,它標識指定日期範圍之間是否存在任何共享持續時間。
但是,需要注意的是, 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);
}
現在,讓我們使用代表不同日期範圍的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));
讓我們將結束日期範圍改為完全重疊:
DateTime startJT2 = new DateTime(2024, 12, 16, 0, 0);
DateTime endJT2 = new DateTime(2024, 12, 18, 0, 0);
assertTrue(isOverlapUsingJodaTime(startJT1, endJT1, startJT2, endJT2));
最後,我們將結束日期範圍更改為連續範圍,並且它再次應該返回不重疊的情況:
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. 結論
在本文中,我們探討了在 Java 中檢查兩個日期範圍是否重疊的各種場景、數學公式和方法。理解部分重疊、完全重疊、連續範圍和零範圍持續時間為我們的探索奠定了堅實的基礎。
與往常一樣,程式碼可以 在 GitHub 上取得。