2015-09-21 6 views
14

Sto provando a calcolare una data di scadenza per un contratto di livello di servizio e, allo stesso tempo, devo anche calcolare l'accordo sul livello di servizio nell'altro direzione.Calcolo del "tempo di lavoro" utilizzando CalendarPeriodCollector di TimePeriod.NET fornisce risultati imprevisti

Sono stato alle prese con i calcoli per "orario di lavoro" (vale a dire il tempo in cui il lavoro è possibile durante un set di giorni) e ho deciso di utilizzare una libreria di terze parti chiamata TimePeriodLibrary.NET per l'attività. Ho bisogno di essere in grado di fare due cose:

  • Dato un inizio ed una DateTimeTimeSpan, si dovrebbero ricevere un DateTime di quando una data contratto di servizio è dovuto (data di scadenza).
  • Dato un inizio DateTime e una fine DateTime, si dovrebbe ricevere un TimeSpan di quanto tempo deve essere preso questo accordo sul livello di servizio.

Tutto il codice sorgente (progetto di prova è GitHub). Ho una classe ServiceLevelManager che fa tutto il lavoro. Prende una lista di WorkDays e HolidayPeriods, per capire quali ore sono disponibili per essere lavorate. La classe CalendarPeriodCollector sta dando risultati imprevisti. Le aspettative che funzionano nel determinare la data di scadenza da un intervallo di tempo, non vengono calcolate correttamente al momento del calcolo.

Qualcuno può vedere se sto facendo qualcosa di sbagliato, o se la biblioteca ha un bug?

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

i test di unità che non riescono vengono estratti di seguito:

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

uscita di questo test è

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 

Voglio sapere se sto usando il collettore in modo non corretto, o se il collezionista ha un bug.

+0

Si prega di aggiungere dettagli per cosa intendete esattamente con "non calcolare correttamente". Fornire input di esempio, output previsto e output effettivo. Grazie. –

+0

Ricorda inoltre che questa particolare libreria ha avuto problemi con DST e fusi orari in passato. Vedo nel loro log delle modifiche che la v1.7.0 ha creato degli alloggi per questo, ma c'è molto poco in termini di documentazione. –

+0

@MattJohnson L'unit test nel codice sorgente (Github) mostra le aspettative. Se esegui i test unitari, vedrai quelli che falliscono. – Junto

risposta

5

Sembra una grande libreria per risolvere un problema familiare.

La cosa migliore da fare è generare i periodi nella raccolta periodica per facilitare il debug del problema.

Ho riscritto il test di utilizzare i tipi di base negli esempi da loro documentazione:

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

Questo si traduce in:

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 

Quindi quello che ho notato è che manca l'ultimo periodo.

Se si modifica l'ora di fine del periodo dalle 16:00 alle 18:00 (e quindi si prevede un'ulteriore ora = 24), sarà sufficiente passare. (dovrai anche arrotondare il risultato)

Quindi sembra che i periodi debbano essere completamente coperti dalla durata totale, la copertura parziale non viene contata.Potresti essere in grado di modificare le opzioni della libreria, in alternativa potresti aggiungere ogni ora del giorno lavorativo come separato CollectingHours (hacky)

Speranza che ti avvicini alla risposta che ti serve!

+0

Mi dispiace @Matt Johnson - sembra che tu l'abbia già capito nei commenti. Non li avevo espansi tutti per vederlo. Pubblica la tua risposta e Junto dovrebbe accettarlo al posto mio. –

+0

Quindi sembra che ci sia un bug in TimePeriodLibrary.NET allora? Il codice sorgente non è disponibile su un repository pubblico per tentare di emettere una richiesta di pull. – Junto