给定两个日期范围,确定两个日期是否重叠的最简单或最有效的方法是什么?
例如,假设我们有由DateTime变量StartDate1到EndDate1和StartDate2到EndDate2表示的范围。
给定两个日期范围,确定两个日期是否重叠的最简单或最有效的方法是什么?
例如,假设我们有由DateTime变量StartDate1到EndDate1和StartDate2到EndDate2表示的范围。
当前回答
最简单的
最简单的方法是使用精心设计的专用库进行日期时间工作。
someInterval.overlaps( anotherInterval )
java.time&ThreeTen额外
业务中最好的是内置在Java8和更高版本中的java.time框架。添加ThreeTen Extra项目,该项目用额外的类来补充java.time,特别是我们这里需要的Interval类。
至于这个问题上的语言不可知标签,两个项目的源代码都可以在其他语言中使用(注意它们的许可证)。
间隔
org.threeten.extra.Interval类很方便,但需要日期时间瞬间(java.time.Instant对象),而不是仅日期值。因此,我们继续使用UTC中的一天的第一刻来表示日期。
Instant start = Instant.parse( "2016-01-01T00:00:00Z" );
Instant stop = Instant.parse( "2016-02-01T00:00:00Z" );
创建一个间隔来表示该时间跨度。
Interval interval_A = Interval.of( start , stop );
我们也可以用开始时刻加上持续时间来定义间隔。
Instant start_B = Instant.parse( "2016-01-03T00:00:00Z" );
Interval interval_B = Interval.of( start_B , Duration.of( 3 , ChronoUnit.DAYS ) );
比较和测试重叠很容易。
Boolean overlaps = interval_A.overlaps( interval_B );
您可以将“间隔”与另一个“间隔”或“即时”进行比较:
邻接,邻接包含外壳等于在之后是之前重叠,重叠
所有这些都使用半开放的方法来定义一段时间,其中开始是包容性的,结束是排他性的。
其他回答
我认为最简单的方法是比较EndDate1是否在StartDate2之前,EndDate2是否在StartDate 1之前。
当然,如果您正在考虑StartDate总是在EndDate之前的时间间隔。
这是我使用moment.js的javascript解决方案:
// Current row dates
var dateStart = moment("2014-08-01", "YYYY-MM-DD");
var dateEnd = moment("2014-08-30", "YYYY-MM-DD");
// Check with dates above
var rangeUsedStart = moment("2014-08-02", "YYYY-MM-DD");
var rangeUsedEnd = moment("2014-08-015", "YYYY-MM-DD");
// Range covers other ?
if((dateStart <= rangeUsedStart) && (rangeUsedEnd <= dateEnd)) {
return false;
}
// Range intersects with other start ?
if((dateStart <= rangeUsedStart) && (rangeUsedStart <= dateEnd)) {
return false;
}
// Range intersects with other end ?
if((dateStart <= rangeUsedEnd) && (rangeUsedEnd <= dateEnd)) {
return false;
}
// All good
return true;
对于ruby,我也发现了这一点:
class Interval < ActiveRecord::Base
validates_presence_of :start_date, :end_date
# Check if a given interval overlaps this interval
def overlaps?(other)
(start_date - other.end_date) * (other.start_date - end_date) >= 0
end
# Return a scope for all interval overlapping the given interval, including the given interval itself
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
end
在这里找到它,解释很好->http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails
这是我的解决方案,当值不重叠时返回真值:
X开始1Y端1
开始2B端2
TEST1: (X <= A || X >= B)
&&
TEST2: (Y >= B || Y <= A)
&&
TEST3: (X >= B || Y <= A)
X-------------Y
A-----B
TEST1: TRUE
TEST2: TRUE
TEST3: FALSE
RESULT: FALSE
---------------------------------------
X---Y
A---B
TEST1: TRUE
TEST2: TRUE
TEST3: TRUE
RESULT: TRUE
---------------------------------------
X---Y
A---B
TEST1: TRUE
TEST2: TRUE
TEST3: TRUE
RESULT: TRUE
---------------------------------------
X----Y
A---------------B
TEST1: FALSE
TEST2: FALSE
TEST3: FALSE
RESULT: FALSE
@Bretana给出的数学解很好,但忽略了两个具体细节:
封闭或半开放间隔的方面空间隔
关于区间边界的封闭或开放状态,@Bretana的解对封闭区间有效
(起点A<=终点B)和(终点A>=起点B)
可以重写为半开间隔:
(开始A<结束B)和(结束A>开始B)
这种校正是必要的,因为根据定义,开放区间边界不属于区间的值范围。
关于空间隔,这里上面所示的关系不成立。根据定义不包含任何有效值的空间隔必须作为特殊情况处理。我通过Java时间库Time4J通过以下示例进行了演示:
MomentInterval a = MomentInterval.between(Instant.now(), Instant.now().plusSeconds(2));
MomentInterval b = a.collapse(); // make b an empty interval out of a
System.out.println(a); // [2017-04-10T05:28:11,909000000Z/2017-04-10T05:28:13,909000000Z)
System.out.println(b); // [2017-04-10T05:28:11,909000000Z/2017-04-10T05:28:11,909000000Z)
前导方括号“[”表示封闭的开始,而最后一个括号“)”表示开放的结束。
System.out.println(
"startA < endB: " + a.getStartAsInstant().isBefore(b.getEndAsInstant())); // false
System.out.println(
"endA > startB: " + a.getEndAsInstant().isAfter(b.getStartAsInstant())); // true
System.out.println("a overlaps b: " + a.intersects(b)); // a overlaps b: false
如上所示,空间隔违反了上面的重叠条件(尤其是startA<endB),因此Time4J(以及其他库)必须将其作为特殊的边缘情况来处理,以确保任何任意间隔与空间隔的重叠都不存在。当然,日期间隔(默认情况下在Time4J中是关闭的,但也可以是半开的,就像空日期间隔一样)的处理方式类似。