【问题标题】:Calculate percentage of overlap in TimeSpans [closed]计算 TimeSpan 中的重叠百分比 [关闭]
【发布时间】:2018-01-30 17:40:55
【问题描述】:
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 个给定日期时间的百分比。例如 24.1 和 25.1 的 13.00-14.00...正如眼睛告诉 25) 13-14,可用性为 0...和 ​​24.1 13.00-14.00 其 66.67%。

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

CalcPercentage(Breakstart、breakend、availability start、availability start) 将返回例如 66,67

【问题讨论】:

  • 那么,你的代码在哪里?
  • 我不知道从哪里开始,最简单的方法是如果已经有一个库。
  • 开始的地方是写出一个公式,就好像你要在纸上做一样。确定您需要哪些输入,并将这些参数设置为方法。然后在该方法中实现您的公式:给定必要的输入,计算并返回结果。如果遇到困难,请回到这里! :)
  • 这并不容易。你需要一个算法。
  • 请注意,减去另一个 DateTime frim 会返回它们之间的经过时间。

标签: c# datetime overlap timespan


【解决方案1】:

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

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

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);。

【讨论】:

  • 很好的答案。我过于关注 OP 的方法签名,但您作为一个整体回答了这个问题。我可以看到 Period 有一个 public static List&lt;Period&gt; CondensePeriods(List&lt;Period&gt; periods) 方法,其中包含您的 AvailabilityPercentage 方法中的初始代码。
  • 是的,我的代码可以重构。我将交集和联合的方法提取到Period。我认为Period 应该只包含与单个时期相关的内容,而不是与时期集合相关的内容;但是,可以将冷凝部分提取到单独的方法中。它使代码更具可读性。感谢您的建议。
【解决方案2】:

鉴于您的示例方法签名,您可以执行以下操作:

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();
}

输出

【讨论】:

    猜你喜欢
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-23
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    相关资源
    最近更新 更多