检查日期范围是否触及时间范围

Check if a date range touches a time range

提问人:RBz 提问时间:2/3/2017 最后编辑:RBz 更新时间:2/6/2017 访问量:430

问:

山姆是一名兼职卡车司机。如果他在 0200 小时 - 0600 小时之间开车,他将获得特殊津贴。他的老板想知道这段时间里,哪些旅行都触及了哪些。以下是他最近 4 次旅行的详细信息。

行程 1:

开始日期时间:01-JAN-2017 00.15.00
EndDateTime:03-JAN-2017 01.45.00

感动:真实

行程 2:

开始日期时间:2017 年 1 月 4 日 13.00.00
结束日期时间:2017 年 1 月 5 日 13.00.00

感动:真实

行程 3:

开始日期时间:2017 年 1 月 6 日 00.00.00
结束日期时间:2017 年 1 月 6 日 05.00.00

感动:真实

行程 4:

开始日期时间:2017 年 1 月 6 日 06.01.00
结束日期时间:2017 年 1 月 6 日 23.00.00

触摸:假

我想出了我自己的实现,用于在 java 中找到它,其中几乎没有 if 情况,但我在某处有一种感觉,我正在重新发明轮子。查找日期范围是否触及时间范围的最佳方法是什么?

编辑:添加以下方法

    public boolean isTripTouchingTimeRange(Date startDate, Date endDate, AllowanceDefinition def) {

    int checkConstant = HOURS_IN_A_DAY - (def.getEndMinute() - def.getStartMinute());
    // HOURS_IN_A_DAY = 1440 MINUTES (2400 HRS) ; def.getEndMinute() = 360
    // MINUTES (0600 HRS) ; def.getStartMinute() = 120 MINUTES (0200 HRS)

    DateTime start = new DateTime(startDate);
    DateTime end = new DateTime(endDate);
    if (DateUtil.subtractDates(start.toDate(), end.toDate()) > checkConstant) {
        return true;
    } else if (end.withTimeAtStartOfDay().isAfter(start.withTimeAtStartOfDay())
            && (end.getMinuteOfDay() > def.getStartMinute())) {
        return true;
    } else if (start.getMinuteOfDay() <= def.getEndMinute() && def.getStartMinute() <= end.getMinuteOfDay()) {
        return true;
    }
    return false;
    }
Java 数学 时间 与语言无关的 重叠

评论

0赞 Basil Bourque 2/5/2017
在日期时间工作中,通常明智地使用半开方法来定义时间跨度。在《半开》中,开头是包容的,而结尾是排他性的。这意味着您的特殊时间跨度从凌晨 2 点开始,一直持续到(但不包括)早上 6 点。因此,如果 是 .false06.01.0006.00.00

答:

2赞 MBo 2/3/2017 #1

此问题类似于循环值的交集。

如果您的解决方案使用了很多情况,请考虑此处的三角类比。使用余弦函数可以解决日复一日换行和不平凡的间隔重叠问题。

日期和时间可以转换为类似

TimeAngle = Pi * TimeHrs / 12

请注意,时间范围和行程时间都应“规范化”:

  • 如果行程持续超过一天,则应将其替换为 0..2*Pi 角度间隔。
    在其他情况下:
    • 将行程开始日期的 00:00 定义为 0
    • 获取起始角度。
      例如,对于 06:00,时间角度为(想象 24 小时钟面上的时针角度)
      TripStartAngle = Pi * StartTripTimeHrs / 12Pi*6/12 = Pi/2 = 90 degree
    • 获取结束角度。如果结束角度小于开始角度(由于日期更改),请添加 2*Pi。
      例如,对于 15:00 的时间角为 。
      但对于 03:00 的时间角度是
      TripEndAngle = Pi * EndTripTimeHrs / 12Pi*15/12 = 1.25 * Pi
      Pi*3/12 = Pi/4 - less than starting Pi/2, so add 2*Pi, and result is 2.25*Pi

评论

0赞 RBz 2/5/2017
我不确定我是否完全理解你的答案。我不认为这是一个完整或最好的解决方案,尤其是当我试图将其与我问题中的行程 1 联系起来时。我们将如何解释多次触摸?您能否提供 TimeAngle 概念的详细信息/建议可读性,这样我在评论您的答案时就不会感到赤裸裸。:)
0赞 MBo 2/5/2017
据我了解,您需要确定时间间隔相交的事实,而不是其数量。在这种情况下,算法是合适的。使用这种算法,首先必须“归一化”间隔 - 如果时间跨度大于天,则可以将其替换为全圆角 0..2*Pi。我将举一些时间到角度转换的示例。
0赞 RBz 2/6/2017
添加了我遵循的方法。据我所知,您的方法将我对时间的线性考虑带入了循环方式。我说得对吗?我认为两者的工作方式相同。
0赞 MBo 2/6/2017
似乎这个条件不足以返回 true。区间交集需要两个 if(end.getMinuteOfDay() > def.getStartMinute())
0赞 Jon Sampson 2/3/2017 #2

我应该先用代码写出来,因为我认为这可能更直接(或者至少对我来说很熟悉)以毫秒时间戳来考虑。如果我有时间,我仍然可能会回去这样做。

与此相关的是,我发现《间隔树》是一本有趣的读物,尽管我没有追求它。

我不相信以下内容是正确的,尤其是我关于如何选择 0200/0600 日期部分的“规则”。需要摩尔测试,但是......

如果 Sam 开车 20 小时或更长时间旅行,那么他已经以某种方式触及了时间范围。(例如,行程 1 和 2)

如果 Sam 的旅行时间少于 20 小时,那么我们可以利用他的旅行时间来限制我们的可能性范围。

0200/0600 的日期部分是根据开始日期确定的。如果 startDateTime 的时间部分介于 0000 和 0600(含)之间,则 0200/0600 共享 startDateTime 的日期部分,否则为第二天。

duration = endDateTime - startDateTime mustStartTime = 0200 - duration mustEndTime = 0600 + duration touched = (startDateTime >= mustStartTime && endDateTime <= mustEndTime)

行程 3

  • 开始日期时间: 06-JAN-2017 00.00.00
  • 结束日期时间: 06-JAN-2017 05.00.00
  • 感动:真实

duration = 5 hours mustStartTime = 2100 = 0200 - 5 mustEndTime = 1100 = 0600 + 5 touched = (0000 >= 2100 && 0500 <= 1100) = (true && true)

行程 4

  • 开始日期时间:06-JAN-2017 06.01.00
  • 结束日期时间:2017年1月6日 23.00.00
  • 触摸:假

duration = 16hr 59min mustStartTime = 1001 = 0200 - 16 hr 59 min mustEndTime = 2159 = 0500 + 16 hr 59 min touched = false = (0601 >= 1001 && 2300 <= 2159) = (false && false)

行程 5

  • 开始日期时间:06-JAN-2017 03.00.00
  • 结束日期时间:06-JAN-2017 04.00.00
  • 感动:真实

duration = 1 hour mustStartTime = 0100 = 0200 - 1 hour mustEndTime = 0700 = 0600 + 1 hour touched = true = (0300 >= 0100 && 0400 <= 0700) = (true && true)

行程 6

  • 开始日期时间:06-JAN-2017 00.00.00
  • 结束日期时间:06-JAN-2017 01.30.00
  • 触摸:假

duration = 1hr 30 min mustStartTime = 0030 = 0200 - 1hr 30 min mustEndTime = 0730 = 0600 + 1hr 30 min touched = false = (0000 >= 0030 && 0130 <= 0730) = (false && true)

行程 7

  • 开始日期时间:06-JAN-2017 05.00.00
  • 结束日期时间:06-JAN-2017 06.00.00
  • 感动:真实

duration = 1 hour mustStartTime = 0100 = 0200 - 1 hour mustEndTime = 0700 = 0600 + 1 hour touched = (0500 >= 0100 && 0600 <= 0700) = (true && true)

更新

现在有了代码和测试。所有断言都是正确的!绝对感觉像是车轮重新发明!

public class OverlappingDateRangeUtil {

    /**
     * 1000 ms * 60 s * 60 m
     */
    public static final long MS_IN_AN_HOUR = 1000 * 60 * 60;

    public static final long MS_IN_TWO_HOURS = 2 * MS_IN_AN_HOUR;

    public static final long MS_IN_SIX_HOURS = 3 * MS_IN_TWO_HOURS;

    public static final long MS_IN_TWENTY_HOURS = 20 * MS_IN_AN_HOUR;

    private static boolean tripLongerThanTwentyHours(long duration) {
        return duration >= MS_IN_TWENTY_HOURS;
    }

    private static long getTruncDateFor0200And0600(Date start) {
        Calendar cal = new GregorianCalendar();
        cal.setTime(start);

        int startHour = cal.get(Calendar.HOUR);

        cal.set(Calendar.HOUR, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        boolean after0600 = startHour >=6 && start.getTime() % 60000 > 0;
        if(after0600) {
            cal.add(Calendar.DATE, 1);
        }
        return cal.getTimeInMillis();
    }

    public static boolean dateRangeTouches0200to0600(Date start, Date end) {
        boolean toReturn = false;
        long duration = end.getTime() - start.getTime();
        if(tripLongerThanTwentyHours(duration)) {
            toReturn = true;
        }
        else {
            long truncTestDate = getTruncDateFor0200And0600(start);
            long oh200 = truncTestDate + MS_IN_TWO_HOURS;
            long oh600 = truncTestDate + MS_IN_SIX_HOURS;
            long mustStart = oh200 - duration;
            long mustEnd = oh600 + duration;
            toReturn = start.getTime() >= mustStart && end.getTime() <= mustEnd;
        }
        return toReturn;
    }
}

public class OverlappingDateRangeUtilTest {

    private DateFormat dateTimeFormat;

    @Before
    public void setUp() throws Exception {
        dateTimeFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    }

    @Test
    public void testDateRangeTouches0200to0600() throws ParseException {
        Date trip1Start = dateTimeFormat.parse("01/01/2017 00:15:00");
        Date trip1End = dateTimeFormat.parse("01/03/2017 01:45:00");
        assertTrue(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip1Start, trip1End));

        Date trip2Start = dateTimeFormat.parse("01/04/2017 13:00:00");
        Date trip2End = dateTimeFormat.parse("01/05/2017 13:00:00");
        assertTrue(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip2Start, trip2End));

        Date trip3Start = dateTimeFormat.parse("01/06/2017 00:00:00");
        Date trip3End = dateTimeFormat.parse("01/06/2017 05:00:00");
        assertTrue(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip3Start, trip3End));

        Date trip4Start = dateTimeFormat.parse("01/06/2017 06:01:00");
        Date trip4End = dateTimeFormat.parse("01/06/2017 23:00:00");
        assertFalse(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip4Start, trip4End));

        Date trip5Start = dateTimeFormat.parse("01/06/2017 06:01:00");      
        Date trip5End = dateTimeFormat.parse("01/06/2017 06:01:00");
        assertFalse(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip5Start, trip5End));

        Date trip6Start = dateTimeFormat.parse("01/06/2017 04:00:00");      
        Date trip6End = dateTimeFormat.parse("01/06/2017 04:00:00");
        assertTrue(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip6Start, trip6End));

        Date trip7Start = dateTimeFormat.parse("01/06/2017 03:00:00");      
        Date trip7End = dateTimeFormat.parse("01/06/2017 04:00:00");
        assertTrue(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip7Start, trip7End));

        Date trip8Start = dateTimeFormat.parse("01/06/2017 00:00:00");      
        Date trip8End = dateTimeFormat.parse("01/06/2017 01:30:00");
        assertFalse(OverlappingDateRangeUtil.dateRangeTouches0200to0600(trip8Start, trip8End));
    }

}

评论

0赞 RBz 2/5/2017
这与我尝试过的类似。我唯一可以补充的是那些想知道 20 是从哪里来的(它的 24 -( 6-2))。这样,我们也可以保持 6 和 2 作为配置的灵活性。@Jon 桑普森 正如你所说,感觉有点过分了。
0赞 Basil Bourque 2/4/2017 #3

这个问题并不完整。

异常

在日期时间工作中,我们会遇到异常情况。最常见的夏令时 (DST)。但我们也面临着政客们经常重新定义时区的问题,就像过去几年在土耳其(2016年)、俄罗斯(2016年、2014年、2011年)、委内瑞拉(2016年、2007年)和其他地方发生的那样。

因此,您必须决定如何处理此类异常。例如,在 DST 转换中,一天可以长达 23 或 25 小时。在美国,DST 意味着没有凌晨 2 点或凌晨 2 点发生两次。请注意,DST 转换发生在美国以外的其他地方,在一天中的其他时间。

时区

除非您选择忽略此类异常,否则您不能仅使用日期和时间。您需要时区的上下文来定义日期+时间。

仅在日期时间工作中的日期和时间通常称为“本地日期时间”,意思是任何位置而不是特定位置。因此,这并不代表时间轴上的一个时刻、一个点。本地日期时间:一个模糊的概念,关于一系列可能的时刻,时间超过 26 小时,时区比 UTC 早 14 小时,晚 12 小时。应用时区来确定实际时刻,即时间轴上的特定点。

评论

0赞 RBz 2/5/2017
所有这些都是需要注意的要点。但是,如果我们考虑将确定日期范围是否触及时间范围作为数学问题,我认为,这里没有任何东西可以回答这个问题。
0赞 Basil Bourque 2/6/2017
我回答的要点是,你把这个主题当作一个简单的通用数学问题是幼稚的。日期时间工作很复杂。