3

I'm trying to calculate how many minutes are between two dates while excluding periods of time that are arbitrarily defined and occur weekly. I also need to be able to calculate the reverse, where given a time, calculate X number of minutes forward excluding those time periods.

For example, I may have two periods [Fri 5:31pm - Sat 2:26pm] and [Tuesday 3:37am - Thursday 1:14am] that I don't want to count when figuring out the minutes between two dates and when calculating forward.

I currently have code that does this for only one gap, though it's not super efficient and is becoming a strain on my system. I also need to accommodate multiple defined gaps which I currently do not do.

My code which does this for one gap looks like this (hideStart and hideEnter are the start and end DateTime for the gap, absoluteLowValue is the starting time from which I am calculating the distance or time between):

public int absoluteDistance(DateTime high){
    long totalMinutes = new Duration(absoluteLowValue,high).getStandardMinutes();

    if (!gapHider.isHidingGaps())
        return (int)totalMinutes;

    int minutesPerWeek = 10080;
    long minutesPerHide = new Duration(hideStart, hideEnd).getStandardMinutes();

    long numFullWeeks = totalMinutes/minutesPerWeek;
    long remainder = totalMinutes%minutesPerWeek;

    totalMinutes -= numFullWeeks*minutesPerHide;

    DateTime latestEnd = high;
    if (latestEnd.getDayOfWeek() == hideEnd.getDayOfWeek() && latestEnd.getSecondOfDay() < hideEnd.getSecondOfDay()){
        latestEnd = latestEnd.minusWeeks(1);
    }
    while (latestEnd.getDayOfWeek() != hideEnd.getDayOfWeek())
        latestEnd = latestEnd.minusDays(1);
    latestEnd = latestEnd.withTime(hideEnd.getHourOfDay(),
                                hideEnd.getMinuteOfHour(),
                                hideEnd.getSecondOfMinute(),
                                hideEnd.getMillisOfSecond());

    DateTime latestStart = high;
    if (latestStart.getDayOfWeek() == hideStart.getDayOfWeek() && latestStart.getSecondOfDay() < hideStart.getSecondOfDay()){
        latestStart = latestStart.minusWeeks(1);
    }
    while (latestStart.getDayOfWeek() != hideStart.getDayOfWeek())
        latestStart = latestStart.minusDays(1);
    latestStart = latestStart.withTime(hideStart.getHourOfDay(),
                                    hideStart.getMinuteOfHour(),
                                    hideStart.getSecondOfMinute(),
                                    hideStart.getMillisOfSecond());

    long timeToNearestEnd = new Duration(latestEnd, high).getStandardMinutes();
    long timeToNearestStart = new Duration(latestStart, high).getStandardMinutes();

    if (timeToNearestEnd < remainder){
        totalMinutes -= minutesPerHide;
    }else if (timeToNearestStart < remainder){
        totalMinutes -= new Duration(latestStart, high).getStandardMinutes();
    }

    return (int)totalMinutes;
}

public DateTime timeSinceAbsLow(int index){ 
    if (absoluteLowValue != null){

        if (!gapHider.isHidingGaps())
            return absoluteLowValue.plusMinutes(index);

        DateTime date = absoluteLowValue;
        long minutesPerWeek = 10080;
        long minutesPerHide = new Duration(hideStart, hideEnd).getStandardMinutes();
        int difference = (int)(minutesPerWeek - minutesPerHide);
        int count = 0;

        while (index - count >= difference){
            date = date.plusWeeks(1);
            count += difference;
        }

        int remaining = index - count;

        DateTime nextStart = date;

        while (nextStart.getDayOfWeek() != hideStart.getDayOfWeek())
            nextStart = nextStart.plusDays(1);
        nextStart = nextStart.withTime(hideStart.getHourOfDay(),
                                    hideStart.getMinuteOfHour(),
                                    hideStart.getSecondOfMinute(),
                                    hideStart.getMillisOfSecond());

        long timeDiff = new Duration(date, nextStart).getStandardMinutes();

        if (timeDiff < remaining){
            date = nextStart.plusMinutes((int)minutesPerHide);
            count+= timeDiff;
            remaining = index - count;
        }

        date = date.plusMinutes(remaining);
        return date;
    }
    return new DateTime(); 
}

Is there a better or easier way of doing this process? I imagine that if I add in the amount of logic to loop through a list of "gaps" that it will just slow it down even more. I'm open to not using Jodatime, I just happen to be using that currently. Any help appreciated!

fadsdd
  • 5
  • 3
Owen
  • 151
  • 1
  • 11

1 Answers1

0

If I understood correctly, you want to compute the total time between a start and end date, and subtract one ore more periods. So, you could work with Duration objects and then, in the end, convert to whatever you want (minutes, seconds, etc):

// compute all periods you don't want to count
List<Duration> hideList = new ArrayList<>();
// duration between friday and saturday (assuming they have the values of your example)
hideList.add(new Duration(friday, saturday));
// add as many periods you want

// total duration between start and end dates
Duration totalDuration = new Duration(start, end);

// subtract all periods from total
for (Duration duration : hideList) {
    totalDuration = totalDuration.minus(duration);
}

// convert to total number of minutes
long totalMinutes = totalDuration.getStandardMinutes();

I'm assuming that all dates used in hideList are between start and end dates (but it's easy to check this using isAfter and isBefore methods).


To do the opposite, you add the totalMinutes to the start date, and then sum all the durations in hideList:

// sum total minutes to start
DateTime dt = start.plusMinutes((int) totalMinutes);

// sum all the periods
for (Duration duration : hideList) {
    dt = dt.plus(duration);
}
// dt is the end date

There's one detail: when converting the Duration to a number of minutes, you'll lose the seconds and milliseconds precision, so when doing the opposite algorithm, those fields will be lost. If that's not an issue, just do it as above. But if you want to preserve all the precision, just start by adding the totalDuration object instead of adding the number of minutes:

// sum totalDuratin to start (to preserve seconds and milliseconds precision)
DateTime dt = start.plus(totalDuration);
fadsdd
  • 5
  • 3
  • The gaps in hideList are not guaranteed to be within the confines of the start and end date. The time consuming part of the algorithm is figuring out which Durations are within the time period and then at the end figuring out how much of the last one to include/exclude. While you're answer would work, it wouldn't improve the algorithm being very slow given its constraints. I was more looking for a sort of set theory oriented solution where the overall duration is a set, the gaps are a set, and I can perform a "A union !B" type command to drastically simplify and reduce cost – Owen Mar 01 '18 at 19:02
  • @Owen You can create `interval = new Interval(friday, saturday)` and then `interval.contains(date)` to check if the date is between those dates; or use `DateTime`'s `isBefore` and `isAfter` methods to compare them individually. I don't see how costly those comparisons are, but if you say it's slow, I assume you've already benchmarked and identified it as a bottleneck. Anyway, I don't see another way of doing it with Joda-Time. Good luck! – fadsdd Mar 01 '18 at 19:10