c#检查时间跨度范围是否在时间跨度范围和多少小时之间

c# check if a timespan range is between timespan range and how many hours

假设我有 3 个时间范围:

07:00 - 18:00  
18:00 - 23:00  
23:00 - 07:00  

和代码:

public class TimeShift
{
   public TimeSpan Start { get; set; }
   public TimeSpan End { get; set; }
}

List<TimeShift> shifts = new List<TimeShift>();

如何检查列表中的每一项是否都在上述 3 个范围内以及多少小时内?

例如一个 TimeShift 其中:

Start: 07:00
End:   23:30

那么就是16.5小时

对于上面的例子:

Range 1: 11 hours  
Range 2: 5 hours  
Range 3: 0.5 hours  

您使用了错误的类型。 StartEnd 应该是 DateTime,而不是 TimeSpanEnd.Subtract(Start) 将提供 TimeSpan 作为其结果。 TimeSpan 类型具有提供总小时数、分钟数等的属性。

TimeSpan Properties

我建议在您的 class 中添加一个方法来表示开始和结束之间的实际差异。例如,将其称为时间跨度 TimeDiff。您需要包含一个 if 语句来确认 TimeLater 小于 TimeEnd 并且 TimeEarlier 大于 TimeStart。然后 'TimeDiff = TimeLater - TimeEarlier' 。

TimeLater 是所提供范围的末尾。 TimeEarlier 是提供的范围的开始。

如果您想计算从 TimeEnd 到 TimeStart 的时间跨度,您只需执行检查 TimeEarlier 是否大于 TimeLater 并具有计算差异的逻辑。它将遵循 TimeDiff = (TimeEnd - TimeEarlier) + (TimeLater - TimeEnd)

要完成时间跨度减法,请在所有时间跨度中使用 .Subtract()

我想我找到了解决方案:

private double GetHours(TimeSpan start, TimeSpan end, TimeSpan startTarget, TimeSpan endTarget)
{
   double result = 0;
   if (startTarget >= start && endTarget <= end)
   {
      result = (endTarget - startTarget).TotalHours;
   }

   if ((startTarget >= start && startTarget < end) && endTarget > end)
   {
      result = (end - startTarget).TotalHours;
   }

   if (startTarget < start && (endTarget > start && endTarget <= end))
   {
      result = (endTarget - start).TotalHours;
   }

   if (startTarget < start && endTarget > end)
   {
      result = (end - start).TotalHours;
   }
   return result;
}

这是一个包含测试的解决方案:

计算

public class TimeSpacCalculator
{
    public static TimeSpan GetTimeSpanIntersect(TimeShift input, TimeSpan start, TimeSpan end)
    {
        // Loopsback input from 23:59 - 00:00
        if (input.Start > input.End)
            return GetTimeSpanIntersect(new TimeShift(input.Start, TimeSpan.FromHours(24)), start, end) +
                   GetTimeSpanIntersect(new TimeShift(TimeSpan.FromHours(0), input.End), start, end);

        // Loopsback Shift from 23:59 - 00:00
        if (start > end)
            return GetTimeSpanIntersect(input, new TimeSpan(), end) +
                   GetTimeSpanIntersect(input, start, TimeSpan.FromHours(24));
        if (input.End < start)
            return new TimeSpan();

        if (input.Start > end)
            return new TimeSpan();

        var actualStart = input.Start < start
            ? start
            : input.Start;

        var actualEnd = input.End > end
            ? end
            : input.End;

        return actualEnd - actualStart;
    }
}

public class TimeRange : TimeShift
{
    public TimeRange(string name, TimeSpan start, TimeSpan end) : base(start, end)
    {
        Name = name;
    }

    public string Name { get; set; }
}

public class TimeShift
{
    public TimeShift(TimeSpan start, TimeSpan end)
    {
        Start = start;
        End = end;
    }

    public TimeSpan Start { get; set; }
    public TimeSpan End { get; set; }
}

测试

[TestFixture]
internal class TimShiftTests
{
    [Test]
    [TestCase(7, 23.5, 11, 5, 0.5)]
    [TestCase(22, 7.5, 0.5, 1, 8)]
    public void Test(double inputStartHours, double inputEndHours, double expectedRange1Hours, double expectedRange2Hours, double expectedRange3Hours )
    {
        var input = new TimeShift(TimeSpan.FromHours(inputStartHours), TimeSpan.FromHours(inputEndHours));

        var ranges = new List<TimeRange>
        {
            new TimeRange("Range1", TimeSpan.FromHours(7), TimeSpan.FromHours(18)),
            new TimeRange("Range2", TimeSpan.FromHours(18), TimeSpan.FromHours(23)),
            new TimeRange("Range3", TimeSpan.FromHours(23), TimeSpan.FromHours(7))
        };


        var result = new Dictionary<string, TimeSpan>();

        foreach (var range in ranges)
        {
            var time = TimeSpacCalculator.GetTimeSpanIntersect(input, range.Start, range.End);

            result.Add(range.Name, time);

            Console.WriteLine($"{range.Name}: " + time.TotalHours);
        }

        result["Range1"].Should().Be(TimeSpan.FromHours(expectedRange1Hours));
        result["Range2"].Should().Be(TimeSpan.FromHours(expectedRange2Hours));
        result["Range3"].Should().Be(TimeSpan.FromHours(expectedRange3Hours));
}

对最近的一个项目有类似的需求。这是我解决同样问题的经验。

根据需求,得出以下类。

public interface IRange<T> : IEquatable<T> where T : IComparable {
    T Maximum { get; }
    T Minimum { get; }
}

public sealed class Range<T> : IRange<T>
    where T : IComparable {
    public Range(T minimum, T maximum) {
        Minimum = minimum;
        Maximum = maximum;
    }

    public T Maximum { get; private set; }

    public T Minimum { get; private set; }

    public override string ToString() {
        return string.Format("{{{0} - {1}}}", Minimum, Maximum);
    }

    public override int GetHashCode() {
        return ToString().GetHashCode();
    }

    public override bool Equals(object obj) {
        if (ReferenceEquals(null, obj)) {
            return false;
        }
        return obj is Range<T> && Equals((Range<T>)obj);
    }

    public bool Equals(T other) {
        return object.Equals(this.ToString(), other.ToString());
    }

}

以下扩展方法支持

public static class Range {
    /// <summary>
    /// Create an <seealso cref="IRange&lt;T&gt;"/> using the provided minimum and maximum value
    /// </summary>
    public static IRange<T> Of<T>(T min, T max) where T : IComparable {
        return new Range<T>(min, max);
    }
    /// <summary>
    /// 
    /// </summary>
    public static bool Contains<T>(this IRange<T> range, T value) where T : IComparable {
        return range.Minimum.CompareTo(value) <= 0 && value.CompareTo(range.Maximum) <= 0;
    }
    /// <summary>
    /// 
    /// </summary>
    public static bool IsOverlapped<T>(this IRange<T> range, IRange<T> other, bool inclusive = false) where T : IComparable {
        return inclusive
            ? range.Minimum.CompareTo(other.Maximum) <= 0 && other.Minimum.CompareTo(range.Maximum) <= 0
            : range.Minimum.CompareTo(other.Maximum) < 0 && other.Minimum.CompareTo(range.Maximum) < 0;
    }
    /// <summary>
    /// 
    /// </summary>
    public static IRange<T> GetIntersection<T>(this IRange<T> range, IRange<T> other, bool inclusive = false) where T : IComparable {
        var start = new[] { range.Minimum, other.Minimum }.Max();
        var end = new[] { range.Maximum, other.Maximum }.Min();

        var valid = inclusive ? start.CompareTo(end) < 0 : start.CompareTo(end) <= 0;

        return valid ? new Range<T>(start, end) : null;
    }
}

这是根据您的特定要求改编的测试

[TestClass]
public class TimeShiftTests : MiscUnitTests {
    [TestMethod]
    public void TimeShiftDurationTest() {
        var shifts = new List<string>(){
            "07:00 - 18:00",
            "18:00 - 23:00",
            "23:00 - 07:00"
        }.Select(s => ParseShift(s));

        var timeShift = "07:00 - 23:30";
        var totalExpectedHours = 16.5;
        var input = ParseShift(timeShift);

        var intersections = shifts
            .Select(shift => shift.GetIntersection(input))
            .ToArray();

        intersections.Length.Should().Be(3);

        var actualHours = intersections.Select(range => (range.Maximum - range.Minimum).TotalHours).ToArray();

        var totalActualHours = actualHours.Sum();

        totalActualHours.Should().Be(totalExpectedHours);

        actualHours[0].Should().Be(11);
        actualHours[1].Should().Be(5);
        actualHours[2].Should().Be(0.5);

    }

    private IRange<DateTime> ParseShift(string period, string format = "HH:mm") {
        var tokens = period
            .Split(new[] { "to", "-" }, StringSplitOptions.RemoveEmptyEntries)
            .Select(s => s.Trim().Replace(" ", string.Empty))
            .ToArray();

        if (tokens.Length != 2) throw new FormatException("time period not well formatted");

        var startDate = DateTime.ParseExact(tokens[0], format, CultureInfo.InvariantCulture);
        var stopDate = DateTime.ParseExact(tokens[1], format, CultureInfo.InvariantCulture);

        var beginTime = startDate.TimeOfDay;
        var endTime = stopDate.TimeOfDay;

        if (endTime < beginTime) {
            stopDate = stopDate.AddDays(1);
        }

        return Range.Of(startDate, stopDate);
    }
}