2015-09-21 14 views
14

Próbuję obliczyć termin dla umowy o poziomie usług, a jednocześnie muszę również z powrotem obliczyć umowę o poziomie usług w innym kierunek.Obliczanie "czasu pracy" za pomocą TimePeriod.NET CalendarPeriodCollector daje nieoczekiwane wyniki

Zmagałem się z obliczeniami dotyczącymi "czasu pracy" (tj. Czasu, w którym praca jest możliwa w ciągu zestawu dni), i zdecydowałem się użyć do tego celu biblioteki strony trzeciej o nazwie TimePeriodLibrary.NET. Muszę być w stanie zrobić dwie rzeczy:

  • Biorąc początek DateTime i TimeSpan powinieneś otrzymać DateTime, kiedy data Service Level Agreement wynika (data powodu).
  • Po rozpoczęciu DateTime i końcu DateTime, powinieneś otrzymać TimeSpan, jak długo powinna obowiązywać umowa o poziomie usług.

Cały kod źródłowy (testowy projekt jest na GitHub). Mam klasę ServiceLevelManager, która wykonuje całą pracę. Sporządza listę WorkDays i HolidayPeriods, aby ustalić, które godziny są dostępne do pracy. Klasa CalendarPeriodCollector daje nieoczekiwane wyniki. Oczekiwania, które działają w ustalaniu terminu z określonego przedziału czasowego, nie są obliczane poprawnie po ich obliczeniu.

Czy ktoś może sprawdzić, czy robię coś nie tak, czy biblioteka ma błąd?

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

testy jednostkowe, które są upadających są wyodrębniane poniżej:

[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."); 
    } 
} 

wyjściowa tego testu jest

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 

Chcę wiedzieć, czy jestem nieprawidłowo stosując kolektor, lub jeśli kolektor ma błąd.

+0

Proszę podać szczegóły, co dokładnie oznacza "nie obliczaj poprawnie". Podaj przykładowe dane wejściowe, oczekiwane wyniki i rzeczywiste wyniki. Dzięki. –

+0

Należy również pamiętać, że w tej konkretnej bibliotece wystąpiły problemy z czasem letnim i strefami czasowymi w przeszłości. W ich dzienniku zmian widzę, że v1.7.0 zrobiło dla nich miejsce, ale dokumentacja jest bardzo ograniczona. –

+0

@MattJohnson Testy jednostki w kodzie źródłowym (Github) pokazują oczekiwania. Jeśli przeprowadzisz testy jednostkowe, zobaczysz te, które zawiodły. – Junto

Odpowiedz

5

To wygląda jak świetna biblioteka do rozwiązywania znanego problemu.

Najlepszą czynnością jest wydrukowanie okresów z kolekcji okresów, aby pomóc w usunięciu problemu.

mam przepisany test do korzystania z typów podstawowych w przykładach z ich dokumentacji:

 [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); 
      // 
    } 

To powoduje:

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 

Więc co zauważyłem jest to, że brak ostatniego okresu.

Jeśli zmienisz czas zakończenia swojego okresu z godziny 16:00 na 18:00 (a zatem spodziewasz się dodatkowej godziny = 24), upłynie tylko około. (musisz również zaokrąglić wynik)

Wygląda więc na to, że okresy muszą być całkowicie pokryte przez całkowity czas trwania, częściowe pokrycie nie jest liczone.Możesz mieć możliwość zmiany opcji biblioteki, alternatywnie możesz dodawać każdą godzinę dnia pracy jako oddzielną CollectingHours (hacky)

Mam nadzieję, że zbliżysz się do odpowiedzi, której potrzebujesz!

+0

Przepraszam @Matt Johnson - wygląda na to, że już to wymyśliłeś w komentarzach. Nie rozszerzyłem ich wszystkich, żeby to zobaczyć. Napisz swoją odpowiedź, a Junto powinien ją zaakceptować zamiast mojej. –

+0

Wygląda więc na błąd w TimePeriodLibrary.NET? Kod źródłowy jest niestety niedostępny w publicznym repozytorium, aby spróbować wysłać żądanie ściągnięcia. – Junto

Powiązane problemy