2

I am facing a validation that get's my head smoking quite a bit. I have an Object which I call Downtime for now and it looks like this:

public class Downtime {

  /** The start date of the downtime. */
  private ZonedDateTime downtimeFrom;

  /** The end date of the downtime. */
  private ZonedDateTime downtimeTo;

  /**
   * Gets the downtime from.
   *
   * @return the downtime from
   */
  public ZonedDateTime getDowntimeFrom()
  {
    return downtimeFrom;
  }

  /**
   * Gets the downtime to.
   *
   * @return the downtime to
   */
  public ZonedDateTime getDowntimeTo()
  {
    return downtimeTo;
  }

  /**
   * Sets the downtime from.
   *
   * @param downtimeFrom the new downtime from
   */
  protected void setDowntimeFrom( ZonedDateTime downtimeFrom )
  {
    this.downtimeFrom = downtimeFrom;
  }

  /**
   * Sets the downtime to.
   *
   * @param downtimeTo the new downtime to
   */
  protected void setDowntimeTo( ZonedDateTime downtimeTo )
  {
    this.downtimeTo = downtimeTo;
  }
}

When I create a new downtime over a CRUD implementation, I already validate that the start time is actually before the end time and such things.
Now I have to add the validation that when I create a new Downtime, it doesn't interfere with the already created downtimes. Meaning the start date of the new downtime is not already existent and is not in another downtime. ( between the start and end of an already created downtime ).

So my way of doing this right now, since I am terrible at date/time oriented things when it comes to localization, would be something like this:

private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes){
  // let's just assume I already filtered out that the list only contains the same day. That's actually pretty easy.
  List<ZonedDateTime> dateRange = new LinkedList<>();
  ZonedDateTime newTime = newDowntime.getDowntimeFrom();

  for(Downtime downtime : createdDowntimes){
    ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
    ZonedDateTime downtimeEnd = downtime.getDowntimeTo();

    for(ZonedDateTime start = downtimeStart; !start.isAfter(downtimeEnd); start = start.plusHours(1)){
      dateRange.add(start);
    }
  }
  if(dateRange.contains(newTime)){
    return false;
  }
  return true;
}

The code is written out of my head in here, so there might be syntax errors but I think you can get the idea of what I want.

And now to my question.
This code above seems like such an overhead and I would like to know how I can validate it faster and with less code.

EDIT: Let me provide a clear Example

I have a List of Downtimes like this:

List<Downtime> createdDowntimes = [
{
  start:2015-01-10T00:00Z,
  end:2015-01-10T02:00Z
},
{
  start:2015-01-10T04:00Z,
  end:2015-01-10T06:00Z
},
{
  start:2015-01-10T07:00Z,
  end:2015-01-10T09:00Z
}
]

and then I have the new Downtime I want to create:

Downtime newDowntime = 
{ 
  start:2015-01-10T05:00Z,
  end:2015-01-10T05:30Z
}

In this example the new downtime is not valid since it actually is during a period that is already in another already created downtime.

Hopefully it makes things more clear.

EDIT 2: While the marked duplicate consists of the reason and offers a solution I also want to give credits to Hugo who provided a good answer with considering my criterias.

Here is another solution I prepared which offers a lot of more detailed exception and information handling

/*
 * Collision 1 = the new downtime starts before the created ones but ends in their span
 * Collision 2 = the new downtime starts after created ones and also ends after their span
 * Collision 3 = the new downtime starts after created ones and ends in their span
 */
List<Downtime> collision1 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isAfter( newTimeStart ) )
    .filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );

List<Downtime> collision2 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
    .filter( e -> e.getDowntimeTo().isBefore( newTimeEnd ) ).collect( Collectors.toList() );

List<Downtime> collision3 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
    .filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );

Keep in mind that my "solution" is one of many and also pretty intensive in terms of performance since streams are heavy operations. So if you don't need to know exactly how many collided and why they collided consider Hugo's answer.

Nico
  • 1,727
  • 1
  • 24
  • 42
  • you can use a [period](https://docs.oracle.com/javase/8/docs/api/java/time/Period.html) to check it. might be much easier – XtremeBaumer May 19 '17 at 09:56
  • you can sort the list/set (with start or end time, in both you will ended up with the same order), then jump to the location of the newDowntime and just its validity there with prev. & next items. – Kh.Taheri May 19 '17 at 09:59
  • @XtremeBaumer While I see the general use of `Period` I can't really see how I can use it to be much easier. I don't get the actual dates between start and end and I can't use a Period to check if a given ZonedDateTime is in this period. Could you provide me an example? Maybe I just don't get how to use Period for this matter. – Nico May 19 '17 at 10:01
  • 1
    Possible duplicate of [Determine Whether Two Date Ranges Overlap](http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap) – Ole V.V. May 19 '17 at 11:21
  • 1
    `Period` is no good for this (it has other good uses). @XtremeBaumer – Ole V.V. May 19 '17 at 11:22
  • he could replace downtime with period as they seem to be pretty much the same – XtremeBaumer May 19 '17 at 11:24
  • @XtremeBaumer this is naturally a simplified example. In real my Downtime object has much more stuff like a list of Specifications for the downtime, a reason etc. Also this object get's saved in a db for later statistic where I need the exact start and end. Filtering is also needed. – Nico May 19 '17 at 11:52
  • 1
    @XtremeBaumer, I am afraid you have misunderstood. A `Period` is an amount of time between two dates, like “1 year 7 months 3 days”. The downtime in the question, unless *I* misunderstood, is the time between two clock times on the same day, like “from 17 May 2017 2:15 AM to 17 May 2017 3:50 AM”, so the `Period` would always equal 0 days. They are two pretty different things. – Ole V.V. May 19 '17 at 20:55

3 Answers3

1

Check this question

Or have this in mind: two periods will overlap if and only if

(StartA <= EndB) and (EndA >= StartB)

So you would have to check your new Downtime against all the previous one you have

Community
  • 1
  • 1
Alberto S.
  • 7,409
  • 6
  • 27
  • 46
  • That is what I want. So I have the advantage of filtering the ones I have to only be the ones with the same date ( without time ) and then check the time. I will consider the question you linked. Thank you in advance – Nico May 19 '17 at 10:51
1

Considering that:

Meaning the start date of the new downtime is not already existent and is not in another downtime. (between the start and end of an already created downtime).

In this case, you need to compare the startDate (downtimeFrom) of the new Downtime with all the existent Downtime's:

private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes) {
    // the start of the new downtime
    ZonedDateTime newStartTime = newDowntime.getDowntimeFrom();

    for (Downtime downtime : createdDowntimes) {
        ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
        ZonedDateTime downtimeEnd = downtime.getDowntimeTo();

        if (newStartTime.equals(downtimeStart)) {
            // start date of new downtime already exists
            return false;
        }

        // start date of new downtime is in the existent downtime
        // (existent startDate < new startDate and new startDate < existent endDate)
        if (downtimeStart.isBefore(newStartTime) && newStartTime.isBefore(downtimeEnd)) {
            return false;
        }
    }

    // no invalid cases found, it's valid
    return true;
}

Note: In this code, the Downtime is valid if the new startDate is equals the end of an existent Downtime. If you don't want that, you can change the second if to:

if (downtimeStart.isBefore(newStartTime) && (! newStartTime.isAfter(downtimeEnd))) {
    return false;
}
0

suppose you have to LocalTime objects,

endA represents when the event-A ends and beginB represents when the event-B begins

then

LocalTime endA = ...;//LocalTime.of(13, 00, 00);
LocalTime beginB = ...;//LocalTime.of(12, 00, 00);
Duration duration = Duration.between(endA, beginB);
System.out.println(duration.getSeconds());

if getSeconds() is negative, then they overlap

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97