How to check if time period is overlapping another time period irrespective of AM/PM
Assuming that each interval is within either AM or PM
I am assuming that each interval is either completely within AM (00:00 through 12) or PM (12:00 through 00). I didn’t understand what you meant by “(+/- 30 mins)”, so I have ignored that.
As an aside I consider this an artificial challenge. In the real world 2 AM and 2 PM are not the same, they just happen to have identical representations on a 12 hour clock. Just as a flag pole and a person from Poland are not the same even though they both have the representation “Pole”.
As Sweeper suggested in a comment I am converting each interval to AM (if it was in PM) before comapring.
LocalTime begin1 = LocalTime.of(1, 0);
LocalTime end1 = LocalTime.of(3, 0);
LocalTime begin2 = LocalTime.of(13, 45);
LocalTime end2 = LocalTime.of(14, 45);
// Convert interval 1 to AM
if (begin1.get(ChronoField.AMPM_OF_DAY) == 1) { // PM
begin1 = begin1.minusHours(12);
end1 = end1.minusHours(12);
}
// validate
if (end1.isBefore(begin1)) {
throw new IllegalStateException("end1 " + end1 + " must not be before begin1 " + begin1);
}
if (end1.isAfter(LocalTime.NOON)) {
throw new IllegalStateException("Interval 1 must be completely within either AM or PM");
}
// Convert interval 2 to AM
if (begin2.get(ChronoField.AMPM_OF_DAY) == 1) {
begin2 = begin2.minusHours(12);
end2 = end2.minusHours(12);
}
// validate
if (end2.isBefore(begin2)) {
throw new IllegalStateException("end2 " + end2 + " must not be before begin2 " + begin2);
}
if (end2.isAfter(LocalTime.NOON)) {
throw new IllegalStateException("Interval 2 must be completely within either AM or PM");
}
if (end2.isAfter(begin1) && end1.isAfter(begin2)) {
System.out.println("They overlap");
} else {
System.out.println("They do not overlap");
}
Output from this code is:
They overlap
Corner case: I am accepting an end time of 12:00 (noon) for an AM interval and of 00:00 for a PM interval. LocalTime.minusHours()
has cyclic underflow, so subtracting 12 hours from 00:00 gives 12:00.
The code may be simpler and easier to find your way through if you define a TimePeriod
class with fields for begin and end, a method for checking overlap and an auxiliary method for converting into AM.
With no restrictions
Edit: Assuming that each interval can be any length from 0 (inclusive) to 24 hours (exclusive) and may cross 00:00, this is somewhat more complicated, but I couldn’t let the challenge rest.
Some observations:
- If one interval is 12 hours or longer and the other has non-zero length, the two necessarily overlap.
- If both intervals are shorter than 12 hours, then if they do not overlap, we can go (count) cyclically forward from
begin1
throughend1
andbegin2
toend2
in the order given here and either not cross 12 o’clock or cross 12 once and end up beforebegin1
. If this cycle doesn’t work, then the intervals must overlap somehow.
In code:
public static boolean overlaps(LocalTime begin1, LocalTime end1, LocalTime begin2, LocalTime end2) {
if (begin1.equals(end1)) { // zero length, cannot overlap anything
return false;
}
if (begin2.equals(end2)) {
return false;
}
// If any interval is 12 hours or longer,
// the other one is necessarily included, that is, overlaps
if (is12HoursOrLonger(begin1, end1)) {
return true;
}
if (is12HoursOrLonger(begin2, end2)) {
return true;
}
// Convert all times to AM
begin1 = toAm(begin1);
end1 = toAm(end1);
begin2 = toAm(begin2);
end2 = toAm(end2);
// For the two intervals *not* to overlap we must be able to go forward
// from begin1 through end1 and begin2 to end2 in this order either
// not crossing 12 or crossing 12 once and ending before or on begin1
boolean crossed12OClock = false;
if (end1.isBefore(begin1)) { // to go forward to end1 we are crossing 12 o’clock
crossed12OClock = true;
}
if (begin2.isBefore(end1)) {
if (crossed12OClock) {
// crossing 12 for the second time;
// intervals cannot be in non-overlapping order
return true;
}
crossed12OClock = true;
}
if (end2.isBefore(begin2)) {
if (crossed12OClock) {
return true;
}
crossed12OClock = true;
}
if (crossed12OClock) {
return end2.isAfter(begin1);
} else {
return false;
}
}
This method uses the following two auxiliary methods:
private static boolean is12HoursOrLonger(LocalTime begin, LocalTime end) {
Duration length = Duration.between(begin, end);
if (length.isNegative()) {
length = length.plusDays(1);
}
return ! length.minusHours(12).isNegative();
}
private static LocalTime toAm(LocalTime time) {
return time.with(ChronoField.AMPM_OF_DAY, 0);
}
Let’s try it out using the times from before:
if (overlaps(begin1, end1, begin2, end2)) {
System.out.println("They overlap");
} else {
System.out.println("They do not overlap");
}
They overlap
Since the code and the arguments are complicated, make sure to cover the methods thoroughly with unit tests.
Determine Whether Two Date Ranges Overlap
(StartA <= EndB) and (EndA >= StartB)
Proof:
Let ConditionA Mean that DateRange A Completely After DateRange B
_ |---- DateRange A ------|
|---Date Range B -----| _
(True if StartA > EndB
)
Let ConditionB Mean that DateRange A is Completely Before DateRange B
|---- DateRange A -----| _
_ |---Date Range B ----|
(True if EndA < StartB
)
Then Overlap exists if Neither A Nor B is true -
(If one range is neither completely after the other,
nor completely before the other,
then they must overlap.)
Now one of De Morgan's laws says that:
Not (A Or B)
<=> Not A And Not B
Which translates to: (StartA <= EndB) and (EndA >= StartB)
NOTE: This includes conditions where the edges overlap exactly. If you wish to exclude that,
change the >=
operators to >
, and <=
to <
NOTE2. Thanks to @Baodad, see this blog, the actual overlap is least of:
{ endA-startA
, endA - startB
, endB-startA
, endB - startB
}
(StartA <= EndB) and (EndA >= StartB)
(StartA <= EndB) and (StartB <= EndA)
NOTE3. Thanks to @tomosius, a shorter version reads:DateRangesOverlap = max(start1, start2) < min(end1, end2)
This is actually a syntactical shortcut for what is a longer implementation, which includes extra checks to verify that the start dates are on or before the endDates. Deriving this from above:
If start and end dates can be out of order, i.e., if it is possible that startA > endA
or startB > endB
, then you also have to check that they are in order, so that means you have to add two additional validity rules:(StartA <= EndB) and (StartB <= EndA) and (StartA <= EndA) and (StartB <= EndB)
or:(StartA <= EndB) and (StartA <= EndA) and (StartB <= EndA) and (StartB <= EndB)
or,(StartA <= Min(EndA, EndB) and (StartB <= Min(EndA, EndB))
or:(Max(StartA, StartB) <= Min(EndA, EndB)
But to implement Min()
and Max()
, you have to code, (using C ternary for terseness),:((StartA > StartB) ? StartA : StartB) <= ((EndA < EndB) ? EndA : EndB)
Check if dates are overlapping and return the maximum count
My answer will consider:
- Given (d3, d5) not overlapping => overlap(d1,d3,d5) = 2 as at a given time only two dates will overlap.
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
class Event {
LocalDate startDate; // inclusive
LocalDate endDate; // inclusive
Event(LocalDate st, LocalDate end) {
this.startDate = st;
this.endDate = end;
}
// Getters & Setters omitted
}
public class Main {
public static void main(String[] args) {
List<Event> events = new ArrayList<Event>();
events.add(new Event(LocalDate.of(2019,1,1), LocalDate.of(2019,5,1))); // d1
events.add(new Event(LocalDate.of(2019,3,1), LocalDate.of(2019,6,1))); // d2
events.add(new Event(LocalDate.of(2019,2,1), LocalDate.of(2019,7,1))); // d3
events.add(new Event(LocalDate.of(2019,8,1), LocalDate.of(2019,12,1))); // d4
// d5 do not overlap d3
events.add(new Event(LocalDate.of(2018,12,1), LocalDate.of(2019,1,31))); // d5
Integer startDateOverlaps = events.stream().map(Event::getStartDate).mapToInt(date -> overlap(date, events)).max().orElse(0);
Integer endDateOverlaps = events.stream().map(Event::getEndDate).mapToInt(date -> overlap(date, events)).max().orElse(0);
System.out.println(Integer.max(startDateOverlaps, endDateOverlaps));
}
public static Integer overlap(LocalDate date, List<Event> events) {
return events.stream().mapToInt(event -> (! (date.isBefore(event.startDate) || date.isAfter(event.endDate))) ? 1 : 0).sum();
}
}
We sum each overlapping date (even itself as otherwise (d1, d2, d3) would only count (d2, d3) for d1 check) and test each startDate & endDate.
Calculate duration between multiple date ranges without overlapping date time
Here's my approach:
- by iterating over the rows from the database, create a list of job durations that take the overlaps into account
- if a new row overlaps an existing JobDuration in the list, the existing element will be modified with the new start and/or end date
- this list can now be turned into duration values
Take a look at this code:
class Scratch {
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
public static void main(String[] args) {
List<JobDuration> durations = new ArrayList<>();
List<JobDuration> newJobs = List.of(
//commented because it has type=1
/*JobDuration.of("2022-07-29 08:30:00.414000", "2022-07-29 19:56:33.414000"),*/
JobDuration.of("2022-07-29 15:30:03.412754", "2022-07-29 15:57:03.965432"),
JobDuration.of("2022-07-29 15:40:03.414000", "2022-07-29 16:32:03.004323"),
JobDuration.of("2022-07-29 16:50:03.643231", "2022-07-29 17:35:03.234562")
);
newJobs.stream().forEach(d -> addDuration(durations, d));
Collections.sort(durations, Comparator.comparing(JobDuration::getStart));
System.out.println(durations);
durations.stream().map(JobDuration::getDuration).forEach(System.out::println);
}
private static void addDuration(List<JobDuration> durations, JobDuration newJdr) {
if(durations.isEmpty()) {
durations.add(newJdr);
return;
}
boolean overlap = false;
for(JobDuration j:durations) {
//overlaps at the start?
if(newJdr.start.isBefore(j.start) && newJdr.end.isAfter(j.start)) {
j.start = newJdr.start;
overlap = true;
}
//overlaps at the end?
if(newJdr.start.isBefore(j.end) && newJdr.end.isAfter(j.end)) {
j.end = newJdr.end;
overlap = true;
}
//is completely inside?
if(newJdr.start.isAfter(j.start) && newJdr.end.isBefore(j.end)){
overlap = true;
}
}
if(!overlap) {
//it did not overlap anywhere, so it's a new entry in the list
durations.add(newJdr);
}
}
public static class JobDuration {
LocalDateTime start;
LocalDateTime end;
@Override
public String toString() {
return "JobDuration{" +
"start=" + start +
", end=" + end +
'}';
}
public JobDuration(LocalDateTime pStart, LocalDateTime pEnd) {
start = pStart;
end = pEnd;
}
public LocalDateTime getStart() {
return start;
}
public static JobDuration of(String sStart, String sEnd) {
return new JobDuration(LocalDateTime.parse(sStart, FORMATTER), LocalDateTime.parse(sEnd, FORMATTER));
}
public Duration getDuration() {
return Duration.between(start, end);
}
}
}
Running this code prints the list (with the 2 non-overlapping time frame) and the Duration of each one.
[JobDuration{start=2022-07-29T15:30:03.412754, end=2022-07-29T16:32:03.004323}, JobDuration{start=2022-07-29T16:50:03.643231, end=2022-07-29T17:35:03.234562}]
PT1H1M59.591569S
PT44M59.591331S
This means this code combines the first two entries, because they overlap. The third entry does not overlap anywhere, so it results in its own duration.
Check if time (startdate & enddate) cuts a time period
If you want to stick with long-values, I would do the following
if (!((train.getEndDate() < startdate) || (train.getStartDate() > enddate))) {...}
Alternatively, you might want to convert your long
-vales to java.util.Date
(new Date(long value)
) so that you can use the Date-class.
There is also the very nice Joda time 2.2 API which has an Interval
-class that provides the following function
overlap(ReadableInterval interval)
Gets the overlap between this interval and another interval.
Algorithm to detect overlapping periods
Simple check to see if two time periods overlap:
bool overlap = a.start < b.end && b.start < a.end;
or in your code:
bool overlap = tStartA < tEndB && tStartB < tEndA;
(Use <=
instead of <
if you change your mind about wanting to say that two periods that just touch each other overlap.)
Related Topics
Making Spring-Data-Mongodb Multi-Tenant
Often Big Numbers Become Negative
How to Get All Methods of a Class
Maximum Size of a Method in Java
What Java Ftp Client Library Should I Use
Production Settings File for Log4J
Does Variable = Null Set It for Garbage Collection
Why Does String.Replace Not Work
Why Is Super Class Constructor Always Called
How to Convert Map to Url Query String
Convert Two Dimensional Array to List in Java
Creating Unicode Character from Its Number
Are Two Java Objects with Same Hashcodes Not Necessarily Equal
How to Remove Accents from a Unicode String
Create Bar Chart in Excel with Apache Poi