I should have written this out in code first, as I think this might've been more straightforward (or at least familiar to me) to think about in millisecond timestamps. I still may go back and do so if I have time.
On a related note, I found Interval trees an interesting read although I didn't pursue it.
I am not convinced the following is correct, especially my 'rule' about how to choose the date component of 0200/0600. Needs moar tests, but. . .
If Sam drives 20 or more hours for a trip then he has touched the time range in some fashion. (ex. Trips 1 & 2)
If Sam drives less than 20 hours for a trip then we can limit our range of possibilities using the duration of his trip.
The date component of 0200/0600 is established according to the start date. If the time component of startDateTime is between 0000 and 0600 (inclusive) then 0200/0600 shares the date component of startDateTime, otherwise it is the following day.
duration = endDateTime - startDateTime
mustStartTime = 0200 - duration
mustEndTime = 0600 + duration
touched = (startDateTime >= mustStartTime && endDateTime <= mustEndTime)
Trip 3
- StartDateTime: 06-JAN-2017 00.00.00
- EndDateTime: 06-JAN-2017 05.00.00
- Touched: True
duration = 5 hours
mustStartTime = 2100 = 0200 - 5
mustEndTime = 1100 = 0600 + 5
touched = (0000 >= 2100 && 0500 <= 1100) = (true && true)
Trip 4
- StartDateTime : 06-JAN-2017 06.01.00
- EndDateTime : 06-JAN-2017 23.00.00
- Touched: False
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)
Trip 5
- StartDateTime : 06-JAN-2017 03.00.00
- EndDateTime : 06-JAN-2017 04.00.00
- Touched: True
duration = 1 hour
mustStartTime = 0100 = 0200 - 1 hour
mustEndTime = 0700 = 0600 + 1 hour
touched = true = (0300 >= 0100 && 0400 <= 0700) = (true && true)
Trip 6
- StartDateTime : 06-JAN-2017 00.00.00
- EndDateTime : 06-JAN-2017 01.30.00
- Touched: False
duration = 1hr 30 min
mustStartTime = 0030 = 0200 - 1hr 30 min
mustEndTime = 0730 = 0600 + 1hr 30 min
touched = false = (0000 >= 0030 && 0130 <= 0730) = (false && true)
Trip 7
- StartDateTime : 06-JAN-2017 05.00.00
- EndDateTime : 06-JAN-2017 06.00.00
- Touched: True
duration = 1 hour
mustStartTime = 0100 = 0200 - 1 hour
mustEndTime = 0700 = 0600 + 1 hour
touched = (0500 >= 0100 && 0600 <= 0700) = (true && true)
Update
Now with code & test. All assertions are proper! Definitely feels like wheel reinventing!
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));
}
}