2010-09-30 21 views
20

Devo determinare la durata tra due DateTimes in minuti.C# - Durata tra due DateTimes in minuti

Tuttavia, v'è una leggera torsione:

  • esclude fine settimana
  • contare solo minuti che sono 7:00-07:00. per esempio: [09/30/2010 6:39:00 PM] - [09/30/2010 7:39:00 PM] = 21 Minutes

sto solo attraversando un periodo difficile venire con un modo decente per farlo e avrebbe apprezzato se qualcuno può suggerire qualcosa.

Grazie.


Edit:

ho finito per andare con la soluzione di dtb. C'è solo un caso speciale che doveva essere curato: se l'ora di fine è dopo le 19:00, conta i minuti dalle 7:00 all'ora di fine effettiva.

Ecco come ho modificato:

var minutes = from day in start.DaysInRangeUntil(end) 
       where !day.IsWeekendDay() 
       let st = Helpers.Max(day.AddHours(7), start) 
       let en = (day.DayOfYear == end.DayOfYear ? 
          end : 
          Helpers.Min(day.AddHours(19), end) 
          ) 
       select (en - st).TotalMinutes; 

Ancora una volta, grazie per l'aiuto.

+2

@ 0xA3, non un duplicato di tale domanda, perché quella domanda non riguarda i giorni e gli orari esclusi qui richiesti. –

+2

@Kirk: è vero. Il numero infinito di modi per utilizzare TimeSpan con regole di esclusione personalizzate manterrà le domande di SO in corso per altri 10 anni. –

+0

Bene, allora le persone dovrebbero mostrare il loro funzionamento, come se dovessero farlo a scuola. :) –

risposta

23

È possibile, ovviamente, utilizzare LINQ:

DateTime a = new DateTime(2010, 10, 30, 21, 58, 29); 
DateTime b = a + new TimeSpan(12, 5, 54, 24, 623); 

var minutes = from day in a.DaysInRangeUntil(b) 
       where !day.IsWeekendDay() 
       let start = Max(day.AddHours(7), a) 
       let end = Min(day.AddHours(19), b) 
       select (end - start).TotalMinutes; 

var result = minutes.Sum(); 

// result == 6292.89 

(Nota: è probabilmente bisogno di controllare per un sacco di casi d'angolo, che ho completamente ignorato.)

metodi di supporto:

static IEnumerable<DateTime> DaysInRangeUntil(this DateTime start, DateTime end) 
{ 
    return Enumerable.Range(0, 1 + (int)(end.Date - start.Date).TotalDays) 
        .Select(dt => start.Date.AddDays(dt)); 
} 

static bool IsWeekendDay(this DateTime dt) 
{ 
    return dt.DayOfWeek == DayOfWeek.Saturday 
     || dt.DayOfWeek == DayOfWeek.Sunday; 
} 

static DateTime Max(DateTime a, DateTime b) 
{ 
    return new DateTime(Math.Max(a.Ticks, b.Ticks)); 
} 

static DateTime Min(DateTime a, DateTime b) 
{ 
    return new DateTime(Math.Min(a.Ticks, b.Ticks)); 
} 
+2

+1, continuo a rabbrividire un po 'quando guardo questo codice ma apprezzo l'uso creativo di LINQ. – JaredPar

+0

Potrei suggerire un'estensione 'IsWeekend'? –

+0

Per prima cosa, vorrei dire che questo è davvero un modo molto compatto per gestire questo problema. LINQ è piuttosto lucido. Il mio problema è che stai iterando ogni singolo giorno tra l'inizio e la fine. Se si trattasse di un periodo di 5 anni, si eseguirà un calcolo non necessario. Non fraintendermi, i computer di oggi sono super potenti e potrebbero gestire facilmente una cosa del genere. Ma è facile calcolare le ore totali in un periodo di 7 giorni => 5 * 12 = 60. Calcolare la parte rimanente è complicato, ma non richiederà mai un'iterazione su più di 7 giorni. – dana

5

Prendere l'ora di inizio, ottenere il numero di minuti per la fine di quel giorno (cioè le 7 di sera).

Quindi dalle 7:00 del giorno successivo, contare il numero di giorni per il giorno finale (escluso qualsiasi momento nel giorno di fine).

Calcolare quanti (se presenti) fine settimana sono passati. (Per ogni fine settimana ridurre il conteggio dei giorni di 2).

Fai qualche semplice matematica da lì per ottenere i minuti totali per il conteggio dei giorni.

Aggiungere il tempo supplementare l'ultimo giorno e i giorni di inizio i tempi supplementari.

+0

Sono d'accordo con questo - non è un semplice calcolo in un solo passaggio, ma è pulito e il codice dovrebbe essere abbastanza leggibile. –

+0

Farei un po 'di codice ma a) Questo rovina il divertimento per l'OP, e b) Il mio VS è sul fritz. – Psytronic

4

Provare la seguente funzione DiffRange.

public static DateTime DayStart(DateTime date) 
{ 
    return date.Date.AddHours(7); 
} 

public static DateTime DayEnd(DateTime date) 
{ 
    return date.Date.AddHours(19); 
} 

public static TimeSpan DiffSingleDay(DateTime start, DateTime end) 
{ 
    if (start.Date != end.Date) { 
     throw new ArgumentException(); 
    } 

    if (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday) 
    { 
     return TimeSpan.Zero; 
    } 

    start = start >= DayStart(start) ? start : DayStart(start); 
    end = end <= DayEnd(end) ? end : DayEnd(end); 
    return end - start; 
} 

public static TimeSpan DiffRange(DateTime start, DateTime end) 
{ 
    if (start.Date == end.Date) 
    { 
     return DiffSingleDay(start, end); 
    } 

    var firstDay = DiffSingleDay(start, DayEnd(start)); 
    var lastDay = DiffSingleDay(DayStart(end), end); 

    var middle = TimeSpan.Zero; 
    var current = start.AddDays(1); 
    while (current.Date != end.Date) 
    { 
     middle = middle + DiffSingleDay(current.Date, DayEnd(current.Date)); 
     current = current.AddDays(1); 
    } 

    return firstDay + lastDay + middle; 
} 
+0

Questo produce un risultato molto diverso dalla mia soluzione. Riesci a individuare cosa c'è di sbagliato nel mio codice? – dtb

+0

@dtb, sembra il mio problema. Ho aggiunto 14 invece di 19. Correggere – JaredPar

+0

Oh cool. La tua versione corretta ora restituisce lo stesso risultato per il mio esempio. – dtb

1

Sono sicuro che c'è qualcosa che ho perso.

TimeSpan CalcBusinessTime(DateTime a, DateTime b) 
    { 
    if (a > b) 
    { 
     DateTime tmp = a; 
     a = b; 
     b = tmp; 
    } 

    if (a.TimeOfDay < new TimeSpan(7, 0, 0)) 
     a = new DateTime(a.Year, a.Month, a.Day, 7, 0, 0); 
    if (b.TimeOfDay > new TimeSpan(19, 0, 0)) 
     b = new DateTime(b.Year, b.Month, b.Day, 19, 0, 0); 

    TimeSpan sum = new TimeSpan(); 
    TimeSpan fullDay = new TimeSpan(12, 0, 0); 
    while (a < b) 
    { 
     if (a.DayOfWeek != DayOfWeek.Saturday && a.DayOfWeek != DayOfWeek.Sunday) 
     { 
      sum += (b - a < fullDay) ? b - a : fullDay; 
     } 
     a = a.AddDays(1); 
    } 

    return sum; 
    } 
+0

@dtb, Grazie, speriamo che sia gratis. Forse farò dei test –

1

Era una domanda piuttosto difficile. Per una base, in un approccio diretto, ho scritto il codice qui sotto:

DateTime start = new DateTime(2010, 01, 01, 21, 00, 00); 
DateTime end = new DateTime(2010, 10, 01, 14, 00, 00); 

// Shift start date's hour to 7 and same for end date 
// These will be added after doing calculation: 
double startAdjustmentMinutes = (start - start.Date.AddHours(7)).TotalMinutes; 
double endAdjustmentMinutes = (end - end.Date.AddHours(7)).TotalMinutes; 

// We can do some basic 
// mathematical calculation to find weekdays count: 
// divide by 7 multiply by 5 gives complete weeks weekdays 
// and adding remainder gives the all weekdays: 
int weekdaysCount = (((int)((end.Date - start.Date).Days/7) * 5) 
      + ((end.Date - start.Date).Days % 7)); 
// so we can multiply it by minutes between 7am to 7 pm 
int minutes = weekdaysCount * (12 * 60); 

// after adding adjustment we have the result: 
int result = minutes + startAdjustmentMinutes + endAdjustmentMinutes; 

So che questo non sembra a livello di codice bello, ma non so se è buono per scorrere giorni e ore tra inizio e fine .

1
static int WorkPeriodMinuteDifference(DateTime start, DateTime end) 
{ 
    //easier to only have to work in one direction. 
    if(start > end) 
     return WorkPeriodMinuteDifference(end, start); 
    //if weekend, move to start of next Monday. 
    while((int)start.DayOfWeek % 6 == 0) 
     start = start.Add(new TimeSpan(1, 0, 0, 0)).Date; 
    while((int)end.DayOfWeek % 6 == 0) 
     end = end.Add(new TimeSpan(1, 0, 0, 0)).Date; 
    //Move up to 07:00 or down to 19:00 
    if(start.TimeOfDay.Hours < 7) 
     start = new DateTime(start.Year, start.Month, start.Day, 7, 0, 0); 
    else if(start.TimeOfDay.Hours > 19) 
     start = new DateTime(start.Year, start.Month, start.Day, 19, 0, 0); 
    if(end.TimeOfDay.Hours < 7) 
     end = new DateTime(end.Year, end.Month, end.Day, 7, 0, 0); 
    else if(end.TimeOfDay.Hours > 19) 
     end = new DateTime(end.Year, end.Month, end.Day, 19, 0, 0); 

    TimeSpan difference = end - start; 

    int weeks = difference.Days/7; 
    int weekDays = difference.Days % 7; 
    if(end.DayOfWeek < start.DayOfWeek) 
     weekDays -= 2; 

    return (weeks * 5 * 12 * 60) + (weekDays * 12 * 60) + difference.Hours * 60 + difference.Minutes 
} 
1

mia implementazione :) L'idea è quella di calcolare rapidamente il totale delle settimane, e camminare per il restante settimana di giorno in giorno ...

public TimeSpan Compute(DateTime start, DateTime end) 
{ 
    // constant start/end times per day 
    TimeSpan sevenAM = TimeSpan.FromHours(7); 
    TimeSpan sevenPM = TimeSpan.FromHours(19); 

    if(start >= end) 
    { 
     throw new Exception("invalid date range"); 
    } 

    // total # of weeks 
    int completeWeeks = ((int)(end - start).TotalDays)/7; 

    // starting total 
    TimeSpan total = TimeSpan.FromHours(completeWeeks * 12 * 5); 

    // adjust the start date to be exactly "completeWeeks" past its original start 
    start = start.AddDays(completeWeeks * 7); 

    // walk days from the adjusted start to end (at most 7), accumulating time as we can... 
    for(
     // start at midnight 
     DateTime dt = start.Date; 

     // continue while there is time left 
     dt < end; 

     // increment 1 day at a time 
     dt = dt.AddDays(1) 
    ) 
    { 
     // ignore weekend 
     if((dt.DayOfWeek == DayOfWeek.Saturday) || 
      (dt.DayOfWeek == DayOfWeek.Sunday)) 
     { 
      continue; 
     } 

     // get the start/end time for each day... 
     // typically 7am/7pm unless we are at the start/end date 
     TimeSpan dtStartTime = ((dt == start.Date) && (start.TimeOfDay > sevenAM)) ? 
      start.TimeOfDay : sevenAM; 
     TimeSpan dtEndTime = ((dt == end.Date) && (end.TimeOfDay < sevenPM)) ? 
      end.TimeOfDay : sevenPM; 

     if(dtStartTime < dtEndTime) 
     { 
      total = total.Add(dtEndTime - dtStartTime); 
     } 
    } 

    return total; 
} 
0

non voglio scrivere alcun codice, ma avere un DateTime puoi dire il giorno della settimana, quindi sai quanti weekend ci sono nel tuo intervallo, così puoi dire quanti minuti ci sono in un weekend.

Quindi non sarebbe così difficile ... ovviamente ci deve essere una soluzione a una linea ottimale ... ma penso che tu possa andare in giro con questo.

Ho dimenticato di dire che conoscete anche i minuti tra le 7:00 e le 7:00, quindi tutto ciò che dovete fare è sottrarre la giusta quantità di minuti alla differenza di orario che si ottiene.

1

Utilizzare TimeSpan.TotalMinutes, sottrarre giorni non lavorativi, sottrarre ore superflue.

Problemi correlati