使用TimePeriod.NET的CalendarPeriodCollector计算“工作时间”会产生意外结果

时间:2015-09-21 16:40:33

标签: c# datetime

我正在尝试计算服务水平协议的截止日期,同时,我还需要在另一个方向上重新计算服务水平协议。

我一直在努力计算“工作时间”(即在一系列日子里工作的时间),并决定使用名为TimePeriodLibrary.NET的第三方库来完成任务。我需要做两件事:

  • 如果有一个开始DateTime和一个TimeSpan,您应该在服务级别协议日期到期时(截止日期)收到DateTime
  • 鉴于开始DateTime和结束DateTime,您应该收到服务级别协议应该花费多长时间TimeSpan

所有源代码(测试项目都在GitHub上)。我有一个ServiceLevelManager课程来完成所有工作。它会列出WorkDaysHolidayPeriods的列表,以便确定可以使用的小时数。 CalendarPeriodCollector类正在给出意想不到的结果。在从时间跨度确定到期日期时起作用的期望在我重新计算时无法正确计算。

任何人都可以看到我做错了什么,或者图书馆是否有错误?

namespace ServicePlanner
{
    using System;
    using System.Collections.Generic;
    using Itenso.TimePeriod;

    public class ServicePlannerManager
    {
        public ServicePlannerManager(IEnumerable<WorkDay> workDays, IEnumerable<HolidayPeriod> holidays)
        {
            this.WorkDays = workDays;
            this.Holidays = holidays;
        }

        public IEnumerable<WorkDay> WorkDays { get; set; }

        public IEnumerable<HolidayPeriod> Holidays { get; set; }

        public TimeSpan GetRemainingWorkingTime(DateTime start, DateTime dueDate)
        {
            var filter = new CalendarPeriodCollectorFilter();
            foreach (var dayOfWeek in this.WorkDays)
            {
                filter.CollectingDayHours.Add(new DayHourRange(dayOfWeek.DayOfWeek, new Time(dayOfWeek.StartTime), new Time(dayOfWeek.EndTime)));
            }

            foreach (var holiday in this.Holidays)
            {
                filter.ExcludePeriods.Add(new TimeBlock(holiday.StartTime, holiday.EndTime));
            }

            var range = new CalendarTimeRange(start, dueDate);
            var collector = new CalendarPeriodCollector(filter, range);
            collector.CollectHours();

            var duration = collector.Periods.GetTotalDuration(new TimeZoneDurationProvider(TimeZoneInfo.FindSystemTimeZoneById("UTC")));
            return duration;
            //var rounded = Math.Round(duration.TotalMinutes, MidpointRounding.AwayFromZero);
            //return TimeSpan.FromMinutes(rounded);
        }
    }
}

下面提取了失败的单元测试:

[TestFixture]
public class ServicePlannerManagerTest
{
        [Test, TestCaseSource("LocalSource")]
    public void GetRemainingWorkingTimeWithHolidayShouldOnlyEnumerateWorkingTime(DateTime startTime, TimeSpan workingHours, DateTime expectedDueDate, string expectation)
    {
        // Arrange
        var workDays = new List<WorkDay>
        { 
            new WorkDay(DayOfWeek.Monday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
            new WorkDay(DayOfWeek.Tuesday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
            new WorkDay(DayOfWeek.Wednesday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
            new WorkDay(DayOfWeek.Thursday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
            new WorkDay(DayOfWeek.Friday, new DateTime(1, 1, 1, 9, 0, 0), new DateTime(1, 1, 1, 17, 0, 0)),
        };
        var holidayPeriods = new List<HolidayPeriod>
        { 
            new HolidayPeriod(new DateTime(2015, 9, 15, 00, 0, 0), new DateTime(2015, 9, 16, 0, 0, 0))
        };
        var service = new ServicePlannerManager(workDays, holidayPeriods);

        // Act
        var result = service.GetRemainingWorkingTime(startTime, expectedDueDate);

        // Assert - 
        Assert.AreEqual(workingHours.TotalHours, result.TotalHours, expectation);
    }

    protected IEnumerable LocalSource()
    {
        yield return
            new TestCaseData(
                new DateTime(2015, 9, 14, 9, 0, 0),
                new TimeSpan(23, 0, 0),
                new DateTime(2015, 9, 17, 16, 0, 0),
                    "5. Expected 23 hours of working time to end on the 17/09/2015 16:00. Monday to Thursday evening. Just short of 3 full working days by one hour. Tuesday is holiday.");
    }
}

此测试的输出是

5. Expected 23 hours of working time to end on the 17/09/2015 16:00. Monday to Thursday evening. Just short of 3 full working days by one hour. Tuesday is holiday.

Expected: 23.0d
But was:  15.999999999944444d

我想知道我是否错误地使用了收藏家,或者收藏家是否有错误。

1 个答案:

答案 0 :(得分:5)

这看起来像是一个很好的解决熟悉问题的库。

最好的办法是输出句点集合中的句点,以帮助您调试问题。

我已经重写了您的测试,以便在其文档中使用示例中的基本类型:

        [Test, TestCaseSource("LocalSource")]
    public void SO_GetRemainingWorkingTimeWithHolidayShouldOnlyEnumerateWorkingTime(DateTime startTime,
        TimeSpan workingHours, DateTime expectedDueDate, string expectation)
    {
        CalendarPeriodCollectorFilter filter = new CalendarPeriodCollectorFilter();
        filter.Months.Add(YearMonth.September); // only Januaries
        filter.WeekDays.Add(DayOfWeek.Monday); // 
        filter.WeekDays.Add(DayOfWeek.Tuesday); // 
        filter.WeekDays.Add(DayOfWeek.Wednesday); // 
        filter.WeekDays.Add(DayOfWeek.Thursday); // 
        filter.WeekDays.Add(DayOfWeek.Friday); // 
        filter.CollectingHours.Add(new HourRange(9, 17)); // working hours

        CalendarTimeRange testPeriod = new CalendarTimeRange(startTime, expectedDueDate);//new DateTime(2015, 9, 14, 9, 0, 0), new DateTime(2015, 9, 17, 18, 0, 0));
        Console.WriteLine("Calendar period collector of period: " + testPeriod);

        filter.ExcludePeriods.Add(new TimeBlock(new DateTime(2015, 9, 15, 00, 0, 0), new DateTime(2015, 9, 16, 0, 0, 0)));

        CalendarPeriodCollector collector = new CalendarPeriodCollector(filter, testPeriod);
        collector.CollectHours();

        foreach (ITimePeriod period in collector.Periods)
        {
            Console.WriteLine("Period: " + period); // THIS WILL HELP A LOT!
        }
        var result = collector.Periods.GetTotalDuration(new TimeZoneDurationProvider(TimeZoneInfo.FindSystemTimeZoneById("UTC")));

        Console.WriteLine(result);
            //
    }

这导致:

Calendar period collector of period: 14/09/2015 09:00:00 - 17/09/2015 15:59:59 | 3.06:59
Period: 14/09/2015 09:00:00 - 14/09/2015 16:59:59 | 0.07:59
Period: 16/09/2015 09:00:00 - 16/09/2015 16:59:59 | 0.07:59
15:59:59.9999998

所以我注意到最后一段时间不见了。

如果您将期间的结束时间从下午4点更改为下午6点(因此预计额外的小时数= 24),它将会通过。 (你还需要对结果进行舍入)

因此看起来期间需要完全涵盖总持续时间,不计算部分覆盖。您可以更改库的选项,或者您也可以将每个工作日的小时添加为单独的CollectingHours(hacky)

希望能让你更接近你需要的答案!

相关问题