计算 TimeSpans 中的重叠百分比

Calculate percentage of overlap in TimeSpans

BREAK
24.1.2018 13.10 - 24.1.2018 13.30
24.1.2018 19.00 - 26.1.2018 15.00

AVAILABILITY    
24.1 13.00-14.00 = 66.67%
25.1 13.00-14.00 = 0%;

我看到上面的例子有中断。 我想检查这个中断占 2 个给定日期时间的百分比。例如 13.00-14.00 表示 24.1 和 25.1... 目测 25) 13-14,可用性为 0... 24.1 13.00-14.00 为 66.67%。

我如何借助库或代码以编程方式计算这个百分比?

CalcPercentage(Breakstart, breakend, availability start,availability start) that would return for example 66,67

根据您的示例方法签名,您可以这样做:

public static double CalcPercentage(DateTime breakStart, DateTime breakEnd, 
    DateTime availStart, DateTime availEnd)
{
    // "fail or return fast" argument checks
    if (breakStart >= breakEnd || availStart >= availEnd) return 0;
    if (breakStart > availEnd || breakEnd < availStart) return 100;
    if (breakStart < availStart && breakEnd > availEnd) return 0;

    // Calc total minutes available, actual minutes available, and the percentage
    var totalAvailMin = (availEnd - availStart).TotalMinutes;
    var actualAvailMin = 0.0;
    if (availStart < breakStart) actualAvailMin += (breakStart - availStart).TotalMinutes;
    if (availEnd > breakEnd) actualAvailMin += (availEnd - breakEnd).TotalMinutes;
    var percentage = actualAvailMin / totalAvailMin * 100;

    return percentage;
}

使用您的示例数据,此方法的用法可能如下所示:

static void Main()
{
    var breakStart = new DateTime(2018, 1, 24, 13, 10, 0);
    var breakEnd = new DateTime(2018, 1, 24, 13, 30, 0);
    var availStart = new DateTime(2018, 1, 24, 13, 00, 0);
    var availEnd = new DateTime(2018, 1, 24, 14, 00, 0);
    var availPercent = CalcPercentage(breakStart, breakEnd, availStart, availEnd);

    Console.WriteLine($"{availPercent:0.00}%");

    Console.Write("\nPress any key to exit...");
    Console.ReadKey();
}

输出

如果我们想从考虑的可用期的总长度中减去休息期,我们必须确保休息期不会相互重叠,否则我们可能会减去一个时期或其中的一部分两次。

首先,我们需要一个表示句点的类型:

public class Period
{
    public Period(DateTime start, DateTime end)
    {
        Start = start;
        End = end;
    }

    public DateTime Start { get; }
    public DateTime End { get; }
    public TimeSpan Duration => End - Start;

    public Period Intersect(Period other)
    {
        long start = Math.Max(Start.Ticks, other.Start.Ticks);
        long end = Math.Min(End.Ticks, other.End.Ticks);
        if (start > end) { // Periods not overlapping or touching.
            return null;
        }
        return new Period(new DateTime(start), new DateTime(end));
    }

    public Period Union(Period other)
    {
        if (other.Start > End || other.End < Start) { // Periods not overlapping or touching.
            return null;
        }
        return new Period(
            new DateTime(Math.Min(Start.Ticks, other.Start.Ticks)),
            new DateTime(Math.Max(End.Ticks, other.End.Ticks))
        );
    }
}

它还包含交集(=重叠部分)和句点并集的方法。

用单个句点替换重叠或接触的句点:

private List<Period> CondensePeriods(IEnumerable<Period> periods)
{
    List<Period> tmp = periods.ToList();
    for (int i = 0; i < tmp.Count; i++) {
        Period first = tmp[i]; // Compare this period to all following ones.
        // Loop in reverse order because we are removing entries.
        for (int j = tmp.Count - 1; j > i; j--) {
            Period condensed = first.Union(tmp[j]);
            if (condensed != null) { // Periods overlap or are touching.
                // Replace first period with a condensed period.
                tmp[i] = condensed;

                // Remove the other period.
                tmp.RemoveAt(j);
            }
        }
    }
    return tmp;
}

请注意,CondensePeriods 的复杂度为 O(n2),因此并未针对许多休息时间进行优化。

最后我们可以这样计算可用性:

public double AvailabilityPercentage(IEnumerable<Period> breaks, Period period)
{
    // First replace overlapping or touching break periods by single period.
    breaks = CondensePeriods(breaks);

    // Now remove these non-overlapping breaks from the tested period.
    long totalPeriodDuration = period.Duration.Ticks;
    long available = totalPeriodDuration;
    foreach (Period brk in breaks) {
        // Take part of break that lies within the tested period.
        var intersection = brk.Intersect(period);
        if (intersection != null) { // Break is not outside of period.
            available -= intersection.Duration.Ticks;
        }
    }

    return 100.0 * available / totalPeriodDuration;
}

(已测试)

如果你确定你的休息时间永远不会重叠,你可以放弃 breaks = CondensePeriods(breaks);