如何在Java中检查一个时间段是否与另一个时间段重叠

32

如何检查同一天内的时间段是否重叠。

例如:

  1. 上午7:00到10:30与上午10:00到11:30重叠
  2. 上午7:00到10:30与上午8:00到9:00重叠
  3. 上午7:00到10:30与上午5:00到8:00重叠

我们需要更多的细节 - 这取决于您如何存储时间段。它是 java.time.Period 吗?通常,检查两个时间段是否重叠很简单 - 比较每个时间段的开始和结束时间。 - icabod
我没有储存时间段。我想要找到重叠的时间。例如(早上7:00到10:30与早上10:00到11:30重叠),这些时间是重叠的。我有50对这样的时间。所以我想验证每一对与另一对是否重叠。 - Gary
当我说“你是如何存储它”的时候,我指的是数据类型是什么?这会影响可能的答案(正如Visionary在他的回答中提到的那样,如果您使用Joda时间,此功能已经提供)。我们所知道的是,时间段可能是文本字符串“7:00am to 10:30am”,需要解析。或者时间可以存储为Java日期。或者浮点值,其中0.0等于1970-01-01 00:00时期。 - icabod
5个回答

53

这里有一个简单的解决方案,表达为一个实用方法:

public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
    return start1.before(end2) && start2.before(end1);
}

这段代码需要在两个时间段之间至少有一毫秒的共享才会返回true

如果将相邻的时间段视为“重叠”(例如10:00-10:30和10:30-11:00),则逻辑需要稍微调整:

public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
    return !start1.after(end2) && !start2.after(end1);
}

这种逻辑在数据库查询中更常见,但相同的方法适用于任何情境。

一旦你意识到它有多么简单,你首先会自责,然后将其应用起来。


before(和after)的意思是严格在之前。因此,时间段不为零:start1.before(start1) == false - Joop Eggen
1
@JoopEggen 我一直在想会有人对此发表评论需要多长时间。虽然这些示例都没有涵盖这种边缘情况,但我已经编辑了我的答案,提供了一种另类但同样优雅(比你的更好:))的解决方案,如果它们被视为重叠,则可以考虑相邻的周期。 - Bohemian
哈哈,一个布尔值专家;对于我的 == false 表示歉意 ;). 不过第一个解决方案似乎最合适。 - Joop Eggen

23

简而言之

( startA.isBefore( stopB ) ) && ( stopA.isAfter( startB ) ) 

LocalTime

如果你想在没有日期和时区上下文的情况下使用通用的时间,可以使用LocalTime类。

LocalTime startA = LocalTime.of( 7 , 0 );
LocalTime stopA = LocalTime.of( 10 , 30 );

LocalTime startB = LocalTime.of( 10 , 0 );
LocalTime stop2B = LocalTime.of( 11 , 30 );

验证数据,确保结束时间在开始时间之后(或相等)。更简洁的说法是“开始时间不晚于结束时间”。

Boolean validA = ( ! startA.isAfter( stopA ) ) ;
Boolean validB = ( ! startB.isAfter( stop2B ) ) ;

根据Meno Hochschildthis Answer,使用半开区间方法来定义时间跨度,其中起点是包含的而终点是不包含的,我们可以使用以下逻辑: (StartA < EndB) and (EndA > StartB)
Boolean overlaps = ( 
    ( startA.isBefore( stopB ) ) 
    && 
    ( stopA.isAfter( startB ) ) 
) ;

请注意,LocalTime 受到单一通用的 24 小时限制。时间不能跨过午夜,也不能回到另一个时间。没有其他日期需要考虑。验证您的输入以验证开始时间在结束时间之前,或它们相等(如果符合您的业务规则)。
if( stopA.isBefore( startA ) ) { … handle error  } 

if( stopB.isBefore( startB ) ) { … handle error  } 

ZonedDateTime

如果您想在时间轴上测试实际时刻,您必须将这些时间对象调整为日期和时区的上下文。应用ZoneId以获取ZonedDateTime对象。

ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );
ZonedDateTime zdt = ZonedDateTime.of( today , startA , z);

Table of all date-time types in Java, both modern and legacy


关于java.time

java.time框架内置于Java 8及更高版本。这些类取代了老旧的传统日期时间类,如java.util.DateCalendarSimpleDateFormat

Joda-Time项目现已进入维护模式,建议迁移至java.time类。

要了解更多,请参见Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310
在哪里获取java.time类?

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter以及更多


6
如果时间间隔是开放的(例如,某些过程尚未完成),并且结束日期可能为空:
public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2)
{
    return
            ((null == end2) || start1.before(end2)) &&
            ((null == end1) || start2.before(end1)) ;
}

4
JOda Time已经内置了相关功能。它非常稳定,并且正在JSR路线上取代损坏的Java日历API。您应该考虑使用它。

3

编辑:

以下是可行的方法:

public boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
    return start1.compareTo(end2) <= 0 && end1.compareTo(start2) >= 0;
}

这里有证明供大家尝试:

@Test
public void isOverlapping_base() {
    Assert.assertTrue(isOverlapping(getDate(2014, 1, 1),
            getDate(2014, 3, 31), getDate(2014, 1, 2),
            getDate(2014, 4, 1)));
    
    Assert.assertTrue(isOverlapping(getDate(2014, 1, 2),
            getDate(2014, 4, 1), getDate(2014, 1, 1),
            getDate(2014, 3, 31)));
    
    Assert.assertTrue(isOverlapping(getDate(2014, 1, 1),
            getDate(2014, 4, 1), getDate(2014, 1, 2),
            getDate(2014, 3, 31)));
    
    Assert.assertTrue(isOverlapping(getDate(2014, 1, 2),
            getDate(2014, 3, 31), getDate(2014, 1, 1),
            getDate(2014, 4, 1)));
    
    Assert.assertFalse(isOverlapping(getDate(2014, 1, 1),
            getDate(2014, 1, 31), getDate(2014, 3, 1),
            getDate(2014, 3, 31)));
    
    Assert.assertFalse(isOverlapping(getDate(2014, 3, 1),
            getDate(2014, 3, 31), getDate(2014, 1, 1),
            getDate(2014, 1, 31)));
}

Date getDate(int year, int month, int date) {
    Calendar working = Calendar.getInstance();
    working.set(year, month - 1, date, 0, 0, 0); 
    working.set(Calendar.MILLISECOND, 0);
    return working.getTime();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接