Based on the ideas and solutions of most authors here, I'd like to share my refined solution with a presumably cleaner code:
/**
* Checks if some date is within a time window given by start and end dates
*
* @param checkDate - date to check if its hours and minutes is between the startDate and endDate
* @param startDate - startDate of the time window
* @param endDate - endDate of the time window
* @return - returns true if hours and minutes of checkDate is between startDate and endDate
*/
public static boolean isDateBetweenStartAndEndHoursAndMinutes(Date checkDate, Date startDate, Date endDate) {
if (startDate == null || endDate == null)
return false;
LocalDateTime checkLdt = LocalDateTime.ofInstant(Instant.ofEpochMilli(checkDate.getTime()), ZoneId.systemDefault());
LocalDateTime startLdt = LocalDateTime.ofInstant(Instant.ofEpochMilli(startDate.getTime()), ZoneId.systemDefault());
LocalDateTime endLdt = LocalDateTime.ofInstant(Instant.ofEpochMilli(endDate.getTime()), ZoneId.systemDefault());
// Table of situations:
// Input dates: start (a), end (b), check (c)
// Interpretations:
// t(x) = time of point x on timeline; v(x) = nominal value of x
// Situation A - crossing midnight:
// c INSIDE
// 1) t(a) < t(c) < t(b) | v(b) < v(a) < v(c) // e.g. a=22:00, b=03:00, c=23:00 (before midnight)
// 2) t(a) < t(c) < t(b) | v(c) < v(b) < v(a) // e.g. a=22:00, b=03:00, c=01:00 (after midnight)
// c OUTSIDE
// 3) t(c) < t(a) < t(b) | v(b) < v(c) < v(a) // e.g. a=22:00, b=03:00, c=21:00
// 4) t(a) < t(b) < t(c) | v(b) < v(c) < v(a) // e.g. a=22:00, b=03:00, c=04:00
// ^--- v(b) < v(a) always when shift spans around midnight!
// Situation B - after/before midnight:
// c INSIDE
// 1) t(a) = t(c) < t(b) | v(a) = v(c) < v(b) // e.g. a=06:00, b=14:00, c=06:00
// 2) t(a) < t(c) < t(b) | v(a) < v(c) < v(b) // e.g. a=06:00, b=14:00, c=08:00
// c OUTSIDE
// 3) t(c) < t(a) < t(b) | v(c) < v(a) < v(b) // e.g. a=06:00, b=14:00, c=05:00
// 4) t(a) < t(b) = t(c) | v(a) < v(b) = v(c) // e.g. a=06:00, b=14:00, c=14:00
// 5) t(a) < t(b) < t(c) | v(a) < v(b) < v(c) // e.g. a=06:00, b=14:00, c=15:00
// ^--- v(a) < v(b) if shift starts after midnight and ends before midnight!
// Check for situation A - crossing midnight?
boolean crossingMidnight = endLdt.isBefore(startLdt);
if (crossingMidnight) {
// A.1
if ((startLdt.isBefore(checkLdt) || startLdt.isEqual(checkLdt)) // t(a) < t(c)
&& checkLdt.isBefore(endLdt.plusDays(1))) // t(c) < t(b+1D)
return true;
// A.2
if (startLdt.isBefore(checkLdt.plusDays(1)) // t(a) < t(c+1D)
&& checkLdt.isBefore(endLdt)) // t(c) < t(b)
return true;
// A.3
if (startLdt.isBefore(endLdt.plusDays(1)) // t(a) < t(b+1D)
&& checkLdt.isBefore(startLdt)) // t(c) < t(a)
return false;
// A.4
if (startLdt.isBefore(endLdt.plusDays(1)) // t(a) < t(b+1D)
&& checkLdt.isAfter(endLdt)) // t(b) < t(c)
return false;
} else {
// B.1 + B.2
if ((startLdt.isEqual(checkLdt) || startLdt.isBefore(checkLdt)) // t(a) = t(c) || t(a) < t(c)
&& checkLdt.isBefore(endLdt)) // t(c) < t(b)
return true;
}
return false;
}
For the sake of completeness I've added the conditions of A.3 and A.4, but in productive code you can leave it out.
Now you can simply create your start and end dates, as well as your time you want to check and call this static method. The code would go then as follows:
Date check = new SimpleDateFormat("HH:mm:ss").parse("01:00:00");
Date start = new SimpleDateFormat("HH:mm:ss").parse("20:11:13");
Date end = new SimpleDateFormat("HH:mm:ss").parse("14:49:00");
if (isDateBetweenStartAndEndHoursAndMinutes(check, start, end)) {
Print("checkDate is within start and End date!"); // adjust this true condition to your needs
}
For the TDD aspect I've added unit tests for the scenarios A and B as given above. Please feel free to check it out and report back if you find any errors or spots for optimization.
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
class LogiqDateUtilsTest {
private LocalDateTime startShiftSituationALdt = LocalDateTime.of(0, 1, 1, 22, 0);
private Date startOfShiftSituationA = Date.from(startShiftSituationALdt.atZone(ZoneId.systemDefault()).toInstant());
private LocalDateTime endShiftSituationALdt = LocalDateTime.of(0, 1, 1, 3, 0);
private Date endOfShiftSituationA = Date.from(endShiftSituationALdt.atZone(ZoneId.systemDefault()).toInstant());
private LocalDateTime startShiftSituationBLdt = LocalDateTime.of(0, 1, 1, 6, 0);
private Date startOfShiftSituationB = Date.from(startShiftSituationBLdt.atZone(ZoneId.systemDefault()).toInstant());
private LocalDateTime endShiftSituationBLdt = LocalDateTime.of(0, 1, 1, 14, 0);
private Date endOfShiftSituationB = Date.from(endShiftSituationBLdt.atZone(ZoneId.systemDefault()).toInstant());
@Test
void testSituationA1() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 23, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertTrue(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationA, endOfShiftSituationA));
}
@Test
void testSituationA2() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 1, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertTrue(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationA, endOfShiftSituationA));
}
@Test
void testSituationA3() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 21, 1);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertFalse(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationA, endOfShiftSituationA));
}
@Test
void testSituationA4() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 4, 1);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertFalse(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationA, endOfShiftSituationA));
}
@Test
void testSituationB1() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 6, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertTrue(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationB, endOfShiftSituationB));
}
@Test
void testSituationB2() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 8, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertTrue(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationB, endOfShiftSituationB));
}
@Test
void testSituationB3() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 5, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertFalse(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationB, endOfShiftSituationB));
}
@Test
void testSituationB4() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 14, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertFalse(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationB, endOfShiftSituationB));
}
@Test
void testSituationB5() {
LocalDateTime checkLdt = LocalDateTime.of(0, 1, 1, 15, 0);
Date checkBetween = Date.from(checkLdt.atZone(ZoneId.systemDefault()).toInstant());
assertFalse(isDateBetweenStartAndEndHoursAndMinutes(checkBetween, startOfShiftSituationB, endOfShiftSituationB));
}
}
Cheers!
01:00:00
is greater than20:11:13
and less than14:49:00
considering20:11:13
is always less than14:49:00
. This is given prerequisite. – Lamed01:00:00
is not greater then20:11:13
on the same day, and20:11:13
is never less than14:49:00
. If you are trying to determine if the time is between20:11:13
on one day and14:49:00
on the next day, then you will need to introduce a date into your comparisons. – Adjure20:11:13
is always less than14:49:00
? – Lamed20:11:13
is greater than14:49:00
. "11 minutes before 3 in the afternoon" is not later than "11 minutes after 8 in the evening" on the same day. What am I missing? – Adjure